Add team-shared Claude Code configuration (#42793)

## Summary

- Adds a comprehensive `.claude/` configuration that gives every
engineer Fleet-aware AI assistance out of the box — no MCP servers,
plugins, or external dependencies required
- Converts legacy `.claude/commands/` to skills with YAML frontmatter,
adds new skills, agents, rules, and hooks
- Adds ~2,500 tokens at startup; rules, skill bodies, and agent bodies
load on demand

  ## What's included

  **6 rules** (auto-apply by file path):
- Go backend, frontend, database, API endpoints, and Orbit agent
conventions
- Covers: ctxerr errors, banned imports, `fleethttp.NewClient()`,
`new(expression)` over legacy `server/ptr`, bounded contexts
(`server/activity/`, `server/mdm/`), transaction safety (no
`ds.reader`/`ds.writer` inside tx), terminology (fleets/reports), React
Query, BEM, permissions utilities, and more

  **12 skills** (invoke with `/`):
- `/review-pr`, `/fix-ci`, `/test`, `/find-related-tests`, `/lint` —
review and testing workflows
- `/new-endpoint`, `/new-migration`, `/update-data-dictionary` —
scaffolding and maintenance
- `/fleet-gitops`, `/spec-story`, `/project` — planning and
configuration workflows.
- `/project` includes a minimal self-improvement mechanism. Claude adds
discoveries and gotchas to the workstream context as you work, so each
session starts with slightly richer context than the last.

  **3 agents** (specialized reviewers):
- `go-reviewer` (sonnet, proactive) — Go conventions, ctxerr, auth,
testing
- `frontend-reviewer` (sonnet, proactive) — TypeScript, React Query,
BEM, accessibility
- `fleet-security-auditor` (opus, on-demand) — MDM, auth gaps,
injection, PII exposure

  **4 hooks** (automated):
- PreToolUse guard blocking dangerous commands (`rm -rf`, `force push`,
`pipe-to-shell`)
  - PostToolUse goimports on Go files (`**/*.go`)
  - PostToolUse prettier on frontend files (`frontend/**`)
- PostToolUse `lint-on-save`: auto-fixes with `golangci-lint --fix` /
`eslint --fix`, then runs `make lint-go-incremental` and feeds remaining
violations back to Claude as context for self-correction

**Permissions** — pre-approves safe operations (`test`, `lint`, `build`,
`make`, `git` read, `gh` CLI) and blocks dangerous ones (`force push`,
`rm -rf`)

**README** — includes a Claude Code primer for engineers new to the
tool, full reference for all skills/agents/hooks/rules, customization
guide (how to override skills, agents, model, effort), and contributing
instructions

**DATA-DICTIONARY.md** — updated with 13 recent migrations (March 2026)
that were missing

  ## Not covered (future iterations)

  - `android/` (Android app)
  - `website/` (Sails.js marketing site)
  - `ee/fleetd-chrome/` (Chrome extension)
  - `ee/vulnerability-dashboard/` (legacy Sails dashboard)
  - `third_party/` (forked external code)
  - Documentation workflows (guides, API docs, handbook)
- Fleet-maintained apps (FMA catalog, packaging, `ee/maintained-apps/`)
  - MDM-specific conventions beyond the Go backend rule

  ## How to test

  Pull the `.claude/` folder into your working branch without switching:

```bash
  git checkout origin/cc-setup-teamwide -- .claude/
  claude --debug  # start a session and work normally
  git checkout -- .claude/  # revert when done
  git clean -fd .claude/    # remove new files that weren't on your branch
```

Check the debug log at `~/.claude/debug/` for detailed hook and tool
execution traces.

Try `/test` on a recent change, `/lint` go to lint Go files, or ask
Claude to review your code and watch the `go-reviewer` agent kick in.

  ### Test plan

- [x] Start a new Claude Code session in the Fleet project and run
`/context` to verify loading
  - [x] Type `/` and confirm all 12 skills appear
  - [x] Run `/test` on a small package
  - [x] Edit a `.go` file and verify goimports runs automatically
- [x] Edit a `.go` file with a lint violation and verify `lint-on-save`
auto-fixes it
  - [x] Edit a `.tsx` file and verify prettier runs automatically
- [x] Run a command like `echo test` and verify no permission prompt
(allowed by settings)
  - [x] Verify `git diff` runs without prompt
- [x] Ask Claude to review code and check that the `go-reviewer` agent
is invoked
  - [x] Verify skills
- [x] `/update-data-dictionary` correctly updates `DATA-DICTIONARY.md`
- [x] `/spec-story` fetches issue and follows the process defined in the
skill
    - [x] `/project` detects memory directory and runs in a fork
    - [x] `/review-pr` runs in fork, produces detailed review
    - [x] `/lint go` detects changes and runs appropriate linters
    - [x] `/lint frontend` detects changes and runs appropriate linters
    - [x] `/lint full` runs all linters
- [x] `/test` detects changed packages and runs with correct env vars
    - [x] `/test` runs frontend tests when frontend files changed
- [x] `/find-related-tests` outputs correct test files and go test
commands
    - [x] `/fix-ci` with a real failing CI run URL
    - [x] `/fleet-gitops` provides GitOps context and references
    - [x] `/new-endpoint` scaffolds with correct Fleet patterns
- [x] `/new-migration` creates timestamped file + test file with correct
structure
  - [x] Verify hooks
  - [x] Verify agents
  - [x] Verify rules
  
  ### Hooks test results

<img width="792" height="502" alt="Screenshot 2026-04-04 at 10 16 14 AM"
src="https://github.com/user-attachments/assets/ed066f65-1b79-4faa-a06f-3ce50837f055"
/>

<img width="811" height="693" alt="Screenshot 2026-04-06 at 8 49 28 AM"
src="https://github.com/user-attachments/assets/4513423e-d16c-40c1-a8d8-27f38a87acfd"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated internal developer documentation and Claude Code configuration
for improved development workflows, including coding standards, security
guidelines, testing procedures, and automated code review/formatting
setup.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Carlo 2026-04-06 19:48:07 -04:00 committed by GitHub
parent 0822b55257
commit 86d4162f1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 1953 additions and 70 deletions

View file

@ -1,26 +1,120 @@
## Running Tests
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## About Fleet
Fleet is an open-source platform for IT and security teams: device management (MDM), vulnerability reporting, osquery fleet management, and security monitoring. Go backend, React/TypeScript frontend, manages thousands of devices across macOS, Windows, Linux, iOS, iPadOS, Android, and ChromeOS.
## Architecture
### Backend request flow
HTTP request → `server/service/handler.go` routes → endpoint function (decode request) → service method (auth + business logic) → datastore method (SQL) → response struct
### Key layers
- **Types & interfaces**: `server/fleet/``Service` in `service.go`, `Datastore` in `datastore.go`
- **Service implementations**: `server/service/` — business logic, auth checks
- **Datastore (MySQL)**: `server/datastore/mysql/` — SQL queries, migrations
- **Enterprise features**: `ee/server/service/` — wraps core service with license checks
- **MDM**: `server/mdm/` — Apple, Microsoft, Android device management
- **Frontend**: `frontend/pages/` (routes), `frontend/components/` (reusable UI), `frontend/services/` (API client)
- **CLI tools**: `cmd/fleet/` (server), `cmd/fleetctl/` (management CLI), `orbit/` (agent)
### Enterprise vs core
- Core features: no special build tags, available in all deployments
- Enterprise features: in `ee/` directory, license checks at service layer
- Use `//go:build !premium` for core-only features when needed
## Terminology
The following terms were recently renamed. Use the new terms in conversation and new code, but don't rename existing variables or API parameters without guidance:
- **"Teams" → "Fleets"** — the concept of grouping hosts. Legacy code still uses `team_id`, `teams` table, etc.
- **"Queries" → "Reports"** — what was formerly a "query" in the product is now a "report." The word "query" now refers solely to a SQL query, which is one aspect of a report.
## Fleet-specific patterns
### Go backend
- **Error wrapping**: `ctxerr.Wrap(ctx, err, "description")` — never pkg/errors
- **Request/Response**: lowercase struct types, `Err error` field, `Error()` method returning `r.Err`
- **Endpoint registration**: `ue.POST("/api/_version_/fleet/resource", fn, reqType{})`
- **Authorization**: `svc.authz.Authorize(ctx, entity, fleet.ActionX)` at start of service methods
- **Logging**: slog with `DebugContext/InfoContext/WarnContext/ErrorContext` — never bare slog.Debug/Info/Warn/Error
- **Pointers**: Use Go 1.26 `new(expression)` for pointer values (e.g., `new("value")`, `new(true)`, `new(42)`). Do NOT use the legacy `server/ptr` package in new code — it exists throughout the codebase but is superseded by `new(expr)`.
- **Reference example**: `server/service/vulnerabilities.go`
## Before writing a fix
- Identify WHERE in the request lifecycle the problem manifests (creation vs team-addition vs sync vs query). Fix it there, not at the reproduction step.
- Read the surrounding 100 lines. If similar checks exist nearby, follow their pattern exactly.
- If an endpoint has zero DB interaction, that's intentional. Adding DB calls needs justification.
- Cover ALL entry points for the same operation (single add, batch/GitOps, etc.).
- For declarative/batch endpoints, validate within the incoming payload, not against the DB.
- When checking for duplicates, exclude the current entity to avoid false conflicts on upserts.
- Run `go test ./server/service/` after adding new datastore interface methods — uninitialized mocks crash other tests.
## Development commands
Check the `Makefile` for the full list of available targets. Key ones below.
### Building and running
```bash
# Quick Go tests (no external deps)
go test ./server/fleet/...
# Integration tests
MYSQL_TEST=1 go test ./server/datastore/mysql/...
MYSQL_TEST=1 REDIS_TEST=1 go test ./server/service/...
# Run a specific test
MYSQL_TEST=1 go test -run TestFunctionName ./server/datastore/mysql/...
# Generate boilerplate for a new frontend component, including associated stylesheet, tests, and storybook
./frontend/components/generate -n RequiredPascalCaseNameOfTheComponent -p optional/path/to/desired/parent/directory
make build # Build fleet + fleetctl
make serve # Start dev server (or: make up)
make generate-dev # Webpack watch mode for frontend dev
make deps # Install dependencies
```
## Go code style
### Testing
```bash
go test ./server/fleet/... # Quick (no external deps)
MYSQL_TEST=1 go test ./server/datastore/mysql/... # MySQL integration
MYSQL_TEST=1 REDIS_TEST=1 go test ./server/service/... # Service integration
MYSQL_TEST=1 go test -run TestFunctionName ./server/datastore/mysql/... # Specific test
yarn test # Frontend Jest tests
```
- Prefer `map[T]struct{}` over `map[T]bool` when the map represents a set.
- Convert a map's keys to a slice with `slices.Collect(maps.Keys(m))` instead of manually appending in a loop.
- Avoid `time.Sleep` in tests. Prefer `testing/synctest` to run code in a fake-clock bubble, or use polling helpers, channels, or `require.Eventually`.
- Use `require` and `assert` from `github.com/stretchr/testify` in tests.
- Use `t.Context()` in tests instead of `context.Background()`.
- Use `any` instead of `interface{}`
- Use `math/rand/v2` instead of `math/rand`.
### Linting
```bash
make lint-go-incremental # Go — ONLY changes since branching from main (use after editing)
make lint-go # Go — full (use before committing)
make lint-js # JS/TS linters
```
### Database
```bash
make migration name=CamelCaseName # Create new migration
make db-reset # Reset dev database
```
### CI test bundles
| Bundle | Packages | Env vars |
|--------|----------|----------|
| `fast` | No external deps | none |
| `mysql` | `server/datastore/mysql/...` | `MYSQL_TEST=1` |
| `service` | `server/service/` (unit) | `MYSQL_TEST=1 REDIS_TEST=1` |
| `integration-core` | `server/service/integration_*_test.go` | `MYSQL_TEST=1 REDIS_TEST=1` |
| `integration-enterprise` | `ee/server/service/integration_*_test.go` | `MYSQL_TEST=1 REDIS_TEST=1` |
| `integration-mdm` | MDM integration tests | `MYSQL_TEST=1 REDIS_TEST=1` |
| `fleetctl` | `cmd/fleetctl/...` | varies |
| `vuln` | `server/vulnerabilities/...` | varies |
| `main` | Everything else | varies |
## Skills and agents
Type `/` to see available skills. Key ones: `/test`, `/lint`, `/review-pr`, `/fix-ci`, `/spec-story`, `/new-endpoint`, `/new-migration`, `/bump-migration`, `/project`, `/fleet-gitops`, `/find-related-tests`.
Agents: **go-reviewer** (proactive after Go edits), **frontend-reviewer** (proactive after TS edits), **fleet-security-auditor** (on-demand for auth/MDM/security).
## Documentation
All Fleet documentation lives in this repo. Check these sources before searching the web:
- **`docs/`** — User-facing docs: feature guides, REST API reference, configuration, deployment, contributing
- **`handbook/`** — Internal procedures: engineering practices, company policies, product design
- **`articles/`** — Blog posts and tutorials
## Other references
- Linter config: `.golangci.yml`
- Activity types: `docs/Contributing/reference/audit-logs.md`
- Claude Code setup: `.claude/README.md`

480
.claude/README.md Normal file
View file

@ -0,0 +1,480 @@
# Fleet Claude Code configuration
This directory contains team-shared [Claude Code](https://claude.ai/code) configuration for the Fleet project. Everything here works out of the box with no MCP servers, plugins, or external dependencies required. The full setup adds ~2,500 tokens at startup — rules, skill bodies, and agent bodies only load on demand.
This setup is a starting point. You can customize it by creating `.claude/settings.local.json` (gitignored) to add your own permissions, MCP servers, and plugins. See [Customize your setup](#customize-your-setup) for details.
If you're new to Claude Code, start with the [primer](#claude-code-primer) below. If you already know Claude Code, skip to [what's here](#whats-here).
### Try it on your branch
To test this setup without switching branches, pull the `.claude/` folder into your current working branch:
```bash
# Add the configuration to your branch
git checkout origin/cc-setup-teamwide -- .claude/
# Start a Claude Code session and work normally (use --debug to see hooks firing)
claude --debug
# When you're done testing, fully remove it so nothing ends up in your PR
git checkout -- .claude/
git clean -fd .claude/
```
This drops the full setup (rules, skills, agents, hooks, and permissions) into your working tree. Start a new Claude Code session and everything loads automatically. When you're done, the second command reverts `.claude/` to whatever's on your branch.
To troubleshoot hooks or see exactly what's firing, start with `claude --debug`. Check the debug log at `~/.claude/debug/` for detailed hook and tool execution traces.
### Not covered by this configuration
The following areas have their own conventions and aren't covered by the current rules, hooks, or skills:
- **`website/`** — Fleet marketing website (Sails.js, separate `package.json` and conventions)
- **`ee/fleetd-chrome/`** — Chrome extension for ChromeOS (TypeScript, separate test setup)
- **`ee/vulnerability-dashboard/`** — Vulnerability dashboard (Sails.js/Grunt, legacy patterns)
- **`android/`** — Android app (Kotlin/Gradle, separate build system)
- **`third_party/`** — Forked external code (not Fleet's conventions)
- **Documentation** — Guides, API docs, and handbook documentation workflows
- **Fleet-maintained apps (FMA)** — FMA catalog workflows, maintained-app packaging, and `ee/maintained-apps/` conventions
- **MDM-specific patterns**`server/mdm/` has complex multi-platform patterns (Apple, Windows, Android) beyond what the Go backend rule covers
---
## Claude Code primer
Claude Code is an AI coding assistant that runs in your terminal, VS Code, JetBrains, desktop app, or browser. It reads your codebase, writes code, runs commands, and understands project context through configuration files like the ones in this directory.
### Core concepts
**CLAUDE.md** — Project instructions loaded at session start, like a `.editorconfig` for AI. Claude reads these automatically to understand your project's conventions, architecture, and workflows. There can be multiple: root-level, `.claude/CLAUDE.md`, and user-level `~/.claude/CLAUDE.md`.
**Skills** — Reusable workflows invoked with `/` (e.g., `/test`, `/fix-ci`). Each skill is a `SKILL.md` file with YAML frontmatter that controls when it triggers, which tools it can use, and whether it runs in an isolated context. Skills replace the older `.claude/commands/` format, adding auto-invocation, tool restrictions, and isolated execution.
**Agents (subagents)** — Specialized AI assistants that run in isolated contexts with their own tools and model. Claude can delegate to them automatically (if their description includes "PROACTIVELY") or you can invoke them by name.
**Rules** — Coding conventions that auto-apply based on file paths. When you edit a `.go` file, Go rules load automatically. When you edit `.tsx`, frontend rules load.
**Hooks** — Shell scripts that run automatically on events like editing files (`PostToolUse`) or before running a tool (`PreToolUse`). Our hooks auto-format Go and TypeScript files on every edit.
**MCP servers** — External tool integrations via the Model Context Protocol. Connect Claude to GitHub, databases, documentation search, and other services. These aren't required for the team setup but can enhance your personal workflow.
**Plugins** — Bundled packages of skills, agents, hooks, and MCP configs from the Claude Code marketplace. Like MCP servers, these are optional personal enhancements.
**Memory** — Claude maintains auto-generated memory across sessions at `~/.claude/projects/<project>/memory/`. It remembers patterns, preferences, and lessons learned. View with `/memory`.
### Commands, shortcuts, and session management
**Sessions**
| Action | How |
|--------|-----|
| Start a session | `claude` (terminal) or open in IDE |
| Continue last session | `claude -c` or `/resume` |
| Resume a named session | `claude -r "name"` or `/resume` |
| Rename session | `/rename <name>` |
| Branch conversation | `/branch` (explore alternatives in parallel) |
| Rewind to checkpoint | `Esc` twice, or `/rewind` |
| Export session | `/export` |
| Side question | `/btw <question>` (doesn't affect conversation history) |
**Context** — The context window fills over time. Manage it actively:
| Action | How |
|--------|-----|
| Check context usage | `/context` |
| Compress conversation | `/compact` or `/compact <focus>` (e.g., `/compact keep the migration plan, drop debugging`) |
| Clear and start fresh | `/clear` |
Use `/clear` between unrelated tasks — context pollution degrades quality. Use `/compact` when context gets large. Delegate heavy investigation to subagents to keep the main context clean. Press `Esc` twice to rewind if Claude goes off track.
**Configuration and diagnostics**
| Action | How |
|--------|-----|
| Invoke a skill | Type `/` then select from menu |
| Switch model | `/model` (sonnet/opus/haiku) |
| Set effort level | `/effort` (low/medium/high) |
| Toggle extended thinking | `Option+T` (macOS) / `Alt+T` |
| Cycle permission mode | `Shift+Tab` |
| Enter plan mode | `/plan <description>` or `Shift+Tab` |
| Edit plan externally | `Ctrl+G` |
| Manage permissions | `/permissions` or `/allowed-tools` |
| Open settings | `/config` |
| View diff of changes | `/diff` |
| Check session cost | `/cost` |
| Check version and status | `/status` |
| Run installation health check | `/doctor` |
| List all commands | `/help` |
### Advanced features
**Plan mode** — Separates research from implementation. Claude explores the codebase and writes a plan for your review before making changes. Activate with `Shift+Tab`, `/plan`, or `--permission-mode plan`. Edit the plan externally with `Ctrl+G`.
**Extended thinking** — Gives Claude more reasoning time for complex problems. Toggle with `Option+T` (macOS) / `Alt+T`. Set effort level with `/effort`. Include "ultrathink" in prompts for maximum depth.
**Auto mode** — Uses a background safety classifier to auto-approve safe tool calls without prompting. Cycle to it with `Shift+Tab`. Configure trusted domains and environments in `settings.json` under `autoMode`.
**Permission modes** — A spectrum from restrictive to autonomous:
- `default` — Reads freely, prompts for writes and commands
- `acceptEdits` — Auto-approves file edits, prompts for commands
- `plan` — Read-only exploration
- `auto` — Classifier-based decisions
- `dontAsk` — Auto-denies tools unless pre-approved via `/permissions` or settings
- `bypassPermissions` — No checks (CI/CD use only)
**Headless and CI mode** — Run non-interactively with `claude -p "prompt" --output-format json`. Useful for CI pipelines, batch processing, and scripted workflows.
**Background tasks** — Long-running work continues while you chat. Skills with `context: fork` run in isolated subagents.
**Git worktrees** — Run `claude --worktree` to work in an isolated git worktree so experimental changes don't affect your working directory.
### Settings hierarchy
Settings are applied in this order (highest to lowest priority):
1. **Managed** — Organization-wide policies (IT/admin controlled)
2. **Local**`.claude/settings.local.json` (personal, gitignored)
3. **Project**`.claude/settings.json` (team-shared, checked in)
4. **User**`~/.claude/settings.json` (personal, all projects)
Your local settings override project settings, so you can always customize without affecting the team.
---
## What's here
```
.claude/
├── CLAUDE.md # Project instructions (architecture, patterns, commands)
├── settings.json # Team settings (env vars, permissions, hooks)
├── settings.local.json # Personal overrides (gitignored)
├── README.md # This file
├── rules/ # Path-scoped coding conventions (auto-applied)
│ ├── fleet-go-backend.md # Go: ctxerr, service patterns, logging, testing
│ ├── fleet-frontend.md # React/TS: components, React Query, BEM, interfaces
│ ├── fleet-database.md # MySQL: migrations, goqu, reader/writer
│ ├── fleet-api.md # API: endpoint registration, versioning, error responses
│ └── fleet-orbit.md # Orbit: agent packaging, TUF updates, platform-specific code
├── skills/ # Workflow skills (invoke with /)
│ ├── review-pr/ # /review-pr <PR#>
│ ├── fix-ci/ # /fix-ci <run-url>
│ ├── test/ # /test [filter]
│ ├── find-related-tests/ # /find-related-tests
│ ├── lint/ # /lint [go|frontend]
│ ├── fleet-gitops/ # /fleet-gitops
│ ├── project/ # /project <name>
│ ├── new-endpoint/ # /new-endpoint
│ ├── new-migration/ # /new-migration
│ ├── bump-migration/ # /bump-migration <filename>
│ └── spec-story/ # /spec-story <issue#>
├── agents/ # Specialized AI agents
│ ├── go-reviewer.md # Go reviewer (proactive, sonnet)
│ ├── frontend-reviewer.md # Frontend reviewer (proactive, sonnet)
│ └── fleet-security-auditor.md # Security auditor (on-demand, opus)
└── hooks/ # Automated hooks
├── guard-dangerous-commands.sh # PreToolUse: blocks dangerous commands
├── goimports.sh # PostToolUse: formats Go files
├── prettier-frontend.sh # PostToolUse: formats frontend files
└── lint-on-save.sh # PostToolUse: lints Go/TS and feeds violations back to Claude
```
## Skills reference
Several skills use the `gh` CLI for GitHub operations (PR review, CI diagnosis, issue speccing). Make sure you have [`gh`](https://cli.github.com/) installed and authenticated with `gh auth login`.
| Skill | Usage | What it does |
|-------|-------|-------------|
| `/review-pr` | `/review-pr 12345` | Reviews a PR for correctness, Go idioms, SQL safety, test coverage, and Fleet conventions. Runs in isolated context. Requires `gh`. |
| `/fix-ci` | `/fix-ci https://github.com/.../runs/123` | Diagnoses CI failures in 8 steps: identifies failing suites, fetches logs, classifies failures as stale assertions vs real bugs, fixes stale assertions, and reports real bugs. Requires `gh`. |
| `/test` | `/test` or `/test TestFoo` | Detects which packages changed via `git diff` and runs their tests with the correct env vars (`MYSQL_TEST`, `REDIS_TEST`). |
| `/find-related-tests` | `/find-related-tests` | Maps changed files to their `_test.go` files, integration tests, and test helpers. Outputs exact `go test` commands. |
| `/fleet-gitops` | `/fleet-gitops` | Validates GitOps YAML: osquery queries against Fleet schema, Apple/Windows/Android profiles against upstream references, and software against the Fleet-maintained app catalog. |
| `/project` | `/project android-mdm` | Loads or creates a workstream context file in your Claude memory directory. Includes a minimal self-improvement mechanism — Claude adds discoveries, gotchas, and key file paths as you work, so each session starts with slightly richer context than the last. |
| `/new-endpoint` | `/new-endpoint` | Scaffolds a Fleet API endpoint: request/response structs, endpoint function, service method, datastore interface, handler registration, and test stubs. |
| `/new-migration` | `/new-migration` | Creates a timestamped migration file and test file with proper naming, init registration, and Up function (Down is always a no-op). |
| `/bump-migration` | `/bump-migration YYYYMMDDHHMMSS_Name.go` | Bumps a migration's timestamp to current time when it conflicts with a migration already merged to main. Renames files and updates function names in both migration and test files. |
| `/spec-story` | `/spec-story 12345` | Breaks down a GitHub story into implementable sub-issues: maps codebase impact, decomposes into atomic tasks per layer (migration/datastore/service/API/frontend), and writes specs with acceptance criteria and a dependency graph. Requires `gh`. |
| `/lint` | `/lint` or `/lint go` | Runs the appropriate linters (golangci-lint, eslint, prettier) on recently changed files. Accepts `go`, `frontend`, or a file path to narrow scope. |
### Using `/project` for workstream context
The `/project` skill builds a personal knowledge base for areas of the codebase you work in repeatedly. Use it at the start of a session to load context from previous sessions.
**First use:** `/project software` — no file exists yet, so Claude asks you to describe the workstream, explores the codebase, and creates a context file with key files, patterns, and architecture notes.
**Subsequent sessions:** `/project software` — Claude loads what it knows, summarizes it, and asks what you're working on today.
**As you work:** Claude adds useful discoveries to the project file — gotchas, important file paths, architectural decisions — so the next session starts with richer context.
**Organizing projects:** The name is just a label. Pick the scope that's most useful to you:
| Scope | Example | Good for |
|-------|---------|----------|
| By team area | `/project software`, `/project mdm` | Broad context that accumulates over time. Good if you consistently work in one area. |
| By feature | `/project patch-policies`, `/project android-enrollment` | Focused context for multi-week features. Tracks specific decisions, status, and key files. |
| By issue | `/project 35666-gitops-exceptions` | Narrow, disposable context tied to a specific piece of work. |
Project files are stored per-machine in your Claude memory directory (`~/.claude/projects/`). They're personal — not shared with the team. Context grows gradually (a few lines per session) and Claude auto-truncates at 200 lines / 25KB, so it won't run away.
## Agents reference
### go-reviewer (sonnet, proactive)
Runs automatically after Go file changes. Checks:
- Error handling (ctxerr wrapping, no swallowed errors)
- Database patterns (parameterized queries, reader/writer, and index coverage)
- API conventions (auth checks, response types, and HTTP status codes)
- Test coverage (integration tests for DB code, edge cases)
- Logging (structured slog, no print statements)
### frontend-reviewer (sonnet, proactive)
Runs automatically after TypeScript and React file changes. Checks:
- TypeScript strictness (no `any`, proper type narrowing)
- React Query patterns (query keys, `enabled` option)
- Component structure (4-file pattern, BEM naming)
- Interface consistency (`I` prefix, `frontend/interfaces/` types)
- Accessibility (ARIA attributes, keyboard navigation)
### fleet-security-auditor (opus, on-demand)
Invoke when touching auth, MDM, enrollment, or user data. Uses Opus for deeper adversarial reasoning. Checks:
- API authorization gaps (missing `svc.authz.Authorize` calls)
- MDM profile payload injection
- osquery query injection
- Team permission boundary violations
- Certificate and SCEP handling
- PII in logs, license enforcement bypass
You can add your own agents by creating files in `.claude/agents/` on a branch, or in `~/.claude/agents/` for personal agents that apply across all projects.
## Hooks
Four hooks run automatically:
| Hook | Event | Files | What it does |
|------|-------|-------|-------------|
| `guard-dangerous-commands.sh` | PreToolUse (Bash) | All commands | Blocks `rm -rf /`, force push to main/master, `git reset --hard origin/`, and pipe-to-shell attacks |
| `goimports.sh` | PostToolUse (Edit/Write) | `**/*.go` | Formats with `goimports``gofumpt``gofmt` (first available) |
| `prettier-frontend.sh` | PostToolUse (Edit/Write) | `frontend/**` | Formats with `npx prettier --write` |
| `lint-on-save.sh` | PostToolUse (Edit/Write) | `**/*.go`, `**/*.ts`, `**/*.tsx` | Auto-fixes with `golangci-lint --fix`, then runs `make lint-go-incremental` (only changes since branching from main) and feeds remaining violations back to Claude for self-correction. For TypeScript, runs `eslint --fix` then reports remaining issues. |
Hooks run in order: formatters first (goimports, prettier), then the linter. The linter is non-blocking — it doesn't reject the edit, but Claude sees the output and fixes violations in its next step. All hooks exit gracefully if the tool isn't installed. To add project-level hooks, edit `.claude/settings.json` on a branch. For personal hooks, add them to `~/.claude/settings.json`.
## Rules
Rules auto-apply when you edit files matching their path globs:
| Rule | Paths | Key conventions |
|------|-------|----------------|
| `fleet-go-backend.md` | `server/**/*.go`, `cmd/**/*.go`, `orbit/**/*.go`, `ee/**/*.go`, `pkg/**/*.go`, `tools/**/*.go`, `client/**/*.go`, `test/**/*.go` | ctxerr errors, error types, banned imports, input validation, viewer context, auth pattern, `fleethttp.NewClient()`, `new(expression)` pointers, bounded contexts, and service signatures |
| `fleet-frontend.md` | `frontend/**/*.ts`, `frontend/**/*.tsx` | React Query, component structure, BEM/SCSS, permissions utilities, team context (fleets/reports terminology), notifications, XSS prevention, and string/URL utilities |
| `fleet-database.md` | `server/datastore/**/*.go` | Migration naming and testing, goqu queries, reader/writer, transaction rules (no ds.reader/writer inside tx), parameterized SQL, and batch operations |
| `fleet-api.md` | `server/service/**/*.go` | Endpoint registration, API versioning, and error-in-response pattern |
| `fleet-orbit.md` | `orbit/**/*.go` | Agent architecture, TUF updates, platform-specific code, packaging, keystore, and security considerations |
## Permissions
`settings.json` pre-approves safe operations so you don't get prompted:
**Allowed:** `go test`, `go vet`, `go build`, `golangci-lint`, `yarn test/lint`, `npx prettier/eslint/tsc/jest`, `make test/lint/build/generate/serve/db-*/migration/deps/e2e-*`, `git status/diff/log/show/branch`, and `gh pr/issue/run/api`
**Denied:** `git push --force`, `git push -f`, `rm -rf /`, and `rm -rf ~`
Commands not in either list (like `git commit` or `git push`) will prompt for permission on first use. To pre-approve them, add them to your `.claude/settings.local.json` — see [local settings](#local-settings) below.
## Customize your setup
Everything above works without extra configuration. The sections below describe how to customize your personal experience without affecting the team.
### Model and effort
Change the model or effort level for your current session at any time:
```
/model opus # Switch to Opus for deeper reasoning
/model sonnet # Switch to Sonnet for faster responses
/effort high # More reasoning time
/effort low # Faster, lighter responses
```
Each skill in this setup has an `effort` level tuned for its complexity (e.g., `/spec-story` uses high, `/test` uses low). The skill's effort overrides your session setting while the skill is active, then reverts when it finishes.
To set your default for all sessions, add to `~/.claude/settings.json`:
```json
{
"model": "opus[1m]",
"effortLevel": "high"
}
```
### Override a shared skill
Each skill has `effort` and optionally `model` set in its frontmatter. You can't override a specific skill's frontmatter from settings — but you can override the entire skill by creating a personal copy with the same name at a higher-priority location.
Personal skills (`~/.claude/skills/`) take precedence over project skills (`.claude/skills/`). To override `/test` with a different effort level:
```bash
# Copy the shared skill to your personal config
mkdir -p ~/.claude/skills/test
cp .claude/skills/test/SKILL.md ~/.claude/skills/test/SKILL.md
# Edit the frontmatter to change effort, model, or anything else
```
Your personal version takes priority. The shared version is ignored for you but still works for everyone else.
### Override a shared agent
Same pattern as skills. Personal agents (`~/.claude/agents/`) take precedence over project agents (`.claude/agents/`):
```bash
# Override go-reviewer with your own version
cp .claude/agents/go-reviewer.md ~/.claude/agents/go-reviewer.md
# Edit to change model, tools, or review criteria
```
### Local settings
Create `.claude/settings.local.json` (gitignored) for personal permission overrides. Local settings take priority over project settings in `.claude/settings.json`.
Common things to add:
- Git write permissions (the shared setup only allows read operations)
- MCP server tool permissions
- Additional `make` or `bash` commands specific to your workflow
- Additional hooks
```json
{
"permissions": {
"allow": [
"Bash(git add*)",
"Bash(git commit*)",
"Bash(git push)",
"mcp__github__*",
"mcp__my-mcp-server__*"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "my-personal-hook.sh",
"timeout": 10
}
]
}
]
}
}
```
Local hooks run in addition to shared hooks, not instead of them. Permission rules merge across levels, with deny taking precedence: if the shared settings deny something, local settings can't override it.
### Personal CLAUDE.md
Create a root-level `CLAUDE.md` (gitignored) for personal instructions that apply on top of the shared `.claude/CLAUDE.md`. Use this for preferences like MCP tool mandates, git workflow rules, or personal conventions. Both files load at session start.
### Personal rules
Create rules at `~/.claude/rules/` for conventions that apply across all your projects. Project rules in `.claude/rules/` and personal rules in `~/.claude/rules/` both load — they don't override each other.
### MCP servers
The shared setup doesn't require any MCP servers. Skills use the `gh` CLI for GitHub operations, which works without MCP. However, MCP servers can enhance your workflow:
```bash
# GitHub MCP — richer GitHub integration beyond what gh CLI provides
claude mcp add --transport http github https://api.github.com/mcp
# Semantic code search — understand code structure, not just text patterns
claude mcp add --transport stdio serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context=claude-code --project-from-cwd
# Documentation search — look up third-party library docs
claude mcp add --transport stdio context7 -- npx -y @upstash/context7-mcp@latest
```
After adding an MCP server, grant its tools in your local settings:
```json
{
"permissions": {
"allow": ["mcp__github__*", "mcp__serena__*", "mcp__context7__*"]
}
}
```
### Plugins
Plugins bundle skills, agents, hooks, and MCP configs. Browse and install from the marketplace:
```bash
claude plugins list # Browse available plugins
claude plugins install <name> # Install a plugin
claude plugins remove <name> # Remove a plugin
```
Useful plugins for Fleet development: `gopls-lsp` (Go LSP), `typescript-lsp` (TS LSP), `feature-dev` (code explorer, architect, and reviewer agents), and `security-guidance` (security warnings on sensitive patterns).
### Override precedence summary
| What | Personal location | Behavior |
|------|------------------|----------|
| Skills | `~/.claude/skills/<name>/SKILL.md` | Replaces the project skill with the same name |
| Agents | `~/.claude/agents/<name>.md` | Replaces the project agent with the same name |
| Rules | `~/.claude/rules/<name>.md` | Additive — loads alongside project rules |
| Settings | `.claude/settings.local.json` | Merges with project settings; deny rules can't be overridden |
| Hooks | `.claude/settings.local.json` | Additive — runs alongside project hooks |
| CLAUDE.md | Root `CLAUDE.md` (gitignored) | Additive — loads alongside `.claude/CLAUDE.md` |
| Memory | `~/.claude/projects/*/memory/` | Personal only — not shared |
## Contribute to this configuration
1. Create a branch.
2. Edit files in `.claude/`.
3. Start a new Claude Code session to test. Use `/context` to verify your changes load correctly.
4. Open a PR for review.
### Add a skill
Create `.claude/skills/your-skill/SKILL.md`:
```yaml
---
name: your-skill
description: When to trigger. Use when asked to "do X" or "Y".
allowed-tools: Read, Grep, Glob, Bash(specific command*)
disable-model-invocation: true # Optional: user-only, no auto-trigger
context: fork # Optional: run in isolated subagent
---
Instructions for Claude when this skill is invoked.
Use $ARGUMENTS for user input.
```
### Add a rule
Create `.claude/rules/your-rule.md`:
```yaml
---
paths:
- "path/**/*.ext"
---
# Rule title
- Convention 1
- Convention 2
```
### Add an agent
Create `.claude/agents/your-agent.md`:
```yaml
---
name: your-agent
description: What it does. Include "PROACTIVELY" for auto-invocation.
tools: Read, Grep, Glob, Bash
model: sonnet # or opus for deep reasoning
---
System prompt describing the agent's role and review criteria.
```

View file

@ -0,0 +1,60 @@
---
name: fleet-security-auditor
description: Fleet-specific security analysis covering MDM, osquery, API auth, and device management threat models. Use when touching auth, MDM, enrollment, or user data.
tools: Read, Grep, Glob, Bash
model: opus
---
You are a security engineer specializing in the Fleet codebase. Think like an attacker targeting a device management platform that controls thousands of endpoints.
## Fleet-Specific Threat Categories
### API Authorization
- Missing `svc.authz.Authorize(ctx, entity, fleet.ActionX)` calls in service methods
- Privilege escalation between teams (team admin accessing another team's data)
- IDOR (insecure direct object references) on host, policy, or query IDs
- Viewer context: always derive user identity from `viewer.FromContext(ctx)`, never from request data
### MDM Profile Payloads
- Malicious configuration profiles (Apple .mobileconfig, Windows .xml, Android .json)
- Profile injection that could modify device security settings
- Certificate payloads with untrusted or self-signed certs
- DDM declaration validation against Apple reference
### osquery Query Injection
- SQL injection through scheduled queries or live query parameters
- Queries accessing sensitive host data beyond intended scope
- Query result exfiltration through webhook or logging channels
### Enrollment & Secrets
- Enrollment secret exposure in API responses or logs
- Enrollment secret scoping (must be team-specific, not global)
- Orbit agent authentication token handling
### Certificate & SCEP Handling
- Private key exposure in logs, responses, or error messages
- Certificate chain validation completeness
- SCEP challenge password handling
### Team Permission Boundaries
- Cross-team data leakage in list/search endpoints
- Team isolation violations in batch operations
- Global vs team-scoped resource access
### License Enforcement
- Enterprise features accessible without valid license
- License check bypasses in API or service layer
### PII & Sensitive Data
- Host identifiers, serial numbers, or user emails in log output
- Sensitive MDM payloads in error messages
- Enrollment secrets or API tokens in debug logging
## Output Format
For each finding:
- **Severity**: CRITICAL / HIGH / MEDIUM / LOW
- **Location**: File and line
- **Vulnerability**: What the issue is
- **Exploit scenario**: How an attacker could exploit this in a Fleet deployment
- **Fix**: Specific remediation

View file

@ -0,0 +1,48 @@
---
name: frontend-reviewer
description: Reviews React/TypeScript frontend changes in Fleet for conventions, type safety, component structure, and accessibility. Run PROACTIVELY after modifying frontend files.
tools: Read, Grep, Glob, Bash
model: sonnet
---
You are a frontend code reviewer specialized in Fleet's React/TypeScript codebase. Review changes with knowledge of Fleet's specific patterns and conventions.
## What you check
### TypeScript strictness
- No `any` types — use `unknown` with type guards or proper interfaces
- Interfaces from `frontend/interfaces/` used correctly (IHost, IUser, etc.)
- Proper type narrowing before accessing nullable fields
### React Query patterns
- `useQuery` with proper `[queryKey, dependency]` array and `enabled` option
- `useMutation` for write operations
- No manual useState/useEffect for data fetching when React Query is appropriate
### Component structure
- Follows 4-file pattern: `ComponentName.tsx`, `_styles.scss`, `ComponentName.tests.tsx`, `index.ts`
- New components created with `./frontend/components/generate -n Name -p path`
- Proper named exports (not default exports for new code)
### SCSS / BEM conventions
- `const baseClass = "component-name"` defined at top
- BEM elements: `${baseClass}__element`
- BEM modifiers: `${baseClass}--modifier`
- Styles in `_styles.scss` files
### API service usage
- Uses `sendRequest` from `frontend/services/`
- Endpoint constants from `frontend/utilities/endpoints.ts`
- Proper error handling for API calls
### Accessibility
- ARIA attributes on interactive elements
- Keyboard navigation support
- Semantic HTML elements
## Output format
Organize findings by severity:
1. **Blocking** — must fix before merge (type errors, broken patterns, accessibility violations)
2. **Important** — should fix (convention violations, missing types)
3. **Minor** — style nits and suggestions

View file

@ -1,3 +1,10 @@
---
name: go-reviewer
description: Reviews Go code changes in Fleet for bugs, conventions, and security. Run PROACTIVELY after modifying Go files.
tools: Read, Grep, Glob, Bash
model: sonnet
---
# Go Code Reviewer for Fleet
You are a Go code reviewer specialized in the Fleet codebase. Review code changes with deep knowledge of Fleet's patterns and conventions.

View file

@ -1,38 +0,0 @@
Read the project context file at `~/.fleet/claude-projects/$ARGUMENTS.md`. This contains background, decisions, and conventions for a specific workstream within Fleet.
Also check for a project-specific memory file named `$ARGUMENTS.md` in your auto memory directory (the persistent memory directory mentioned in your system instructions). If it exists, read it too — it contains things learned while working on this project in previous sessions.
If the project context file was found, give a brief summary of what you know and ask what we're working on today.
If the project context file doesn't exist:
1. Tell the user no project named "$ARGUMENTS" was found.
2. List any existing `.md` files in `~/.fleet/claude-projects/` so they can see what's available.
3. Ask if they'd like to initialize a new project with that name.
4. If they don't want to initialize, stop here.
5. If they do, ask them to brain-dump everything they know about the workstream — the goal, what areas of the codebase it touches, key decisions, gotchas, anything they've been repeating at the start of each session. A sentence is fine, a paragraph is better. Also offer: "I can also scan your recent session transcripts for relevant context — would you like me to look back through recent chats?"
6. If they want you to scan prior sessions, look at the JSONL transcript files in the Claude project directory (the same directory as your auto memory, but the `.jsonl` files). Read recent ones (last 5-10), skimming for messages related to the workstream. These are large files, so read selectively — check the first few hundred lines of each to gauge relevance before reading more deeply.
7. Using their description, any prior session context, and codebase exploration, find relevant files, patterns, types, and existing implementations related to the workstream.
8. Create `~/.fleet/claude-projects/$ARGUMENTS.md` populated with what you found, using this structure:
```markdown
# Project: $ARGUMENTS
## Background
<!-- What is this workstream about, in the user's words + what you learned -->
## How It Works
<!-- Key mechanisms, patterns, and code flow you discovered -->
## Key Files
<!-- Important file paths for this workstream, with brief descriptions -->
## Key Decisions
<!-- Important architectural or design decisions -->
## Status
<!-- What's done, what remains -->
```
9. Show the user what you wrote and ask if they'd like to adjust anything before continuing.
As you work on a project, update the memory file (in your auto memory directory, named `$ARGUMENTS.md`) with useful discoveries — gotchas, important file paths, patterns — but not session-specific details.

View file

@ -1,10 +0,0 @@
Run Go tests related to my recent changes. Look at `git diff` and `git diff --cached` to determine which packages were modified.
For each modified package, run the tests with appropriate env vars:
- If the package is under `server/datastore/mysql`: use `MYSQL_TEST=1`
- If the package is under `server/service`: use `MYSQL_TEST=1 REDIS_TEST=1`
- Otherwise: run without special env vars
If an argument is provided, use it as a `-run` filter: $ARGUMENTS
Show a summary of results: which packages passed, which failed, and any failure details.

25
.claude/goimports.sh Executable file
View file

@ -0,0 +1,25 @@
#!/bin/sh
# PostToolUse hook: run goimports on Go files after Edit/Write
# Receives tool event JSON on stdin
INPUT=$(cat)
# Extract file_path with grep to avoid jq parse errors from control chars in tool input
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
case "$FILE_PATH" in
*.go)
if command -v goimports >/dev/null 2>&1; then
goimports -w "$FILE_PATH" 2>/dev/null
elif command -v gofumpt >/dev/null 2>&1; then
gofumpt -w "$FILE_PATH" 2>/dev/null
else
gofmt -w "$FILE_PATH" 2>/dev/null
fi
;;
esac
exit 0

View file

@ -0,0 +1,49 @@
#!/bin/sh
# PreToolUse hook: block dangerous bash commands
# Exit 0 = allow, Exit 2 = block
INPUT=$(cat)
# Extract command with grep to avoid jq parse errors from control chars in tool input
COMMAND=$(printf '%s' "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"command"[[:space:]]*:[[:space:]]*"//;s/"$//')
if [ -z "$COMMAND" ]; then
exit 0
fi
# Block rm -rf with dangerous targets (/, ~, *, bare . but not ./path)
echo "$COMMAND" | grep -qE 'rm\s+-rf\s+/' && {
echo "BLOCKED: rm -rf with absolute path" >&2
exit 2
}
echo "$COMMAND" | grep -qE 'rm\s+-rf\s+~' && {
echo "BLOCKED: rm -rf home directory" >&2
exit 2
}
echo "$COMMAND" | grep -qE 'rm\s+-rf\s+\*' && {
echo "BLOCKED: rm -rf wildcard" >&2
exit 2
}
echo "$COMMAND" | grep -qE 'rm\s+-rf\s+\.$' && {
echo "BLOCKED: rm -rf current directory" >&2
exit 2
}
# Block force push to main/master
echo "$COMMAND" | grep -qiE 'git\s+push\s+.*(--force|-f)\s+.*(main|master)' && {
echo "BLOCKED: force push to main/master" >&2
exit 2
}
# Block hard reset to remote
echo "$COMMAND" | grep -qiE 'git\s+reset\s+--hard\s+origin/' && {
echo "BLOCKED: hard reset to remote" >&2
exit 2
}
# Block pipe-to-shell
echo "$COMMAND" | grep -qiE '(curl|wget)\s+.*\|\s*(ba)?sh' && {
echo "BLOCKED: pipe to shell" >&2
exit 2
}
exit 0

View file

@ -0,0 +1,48 @@
#!/bin/sh
# PreToolUse hook: block dangerous bash commands
# Exit 0 = allow, Exit 2 = block
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if [ -z "$COMMAND" ]; then
exit 0
fi
# Block rm -rf with dangerous targets (/, ~, *, bare . but not ./path)
echo "$COMMAND" | grep -qE 'rm\s+-rf\s+/' && {
echo "BLOCKED: rm -rf with absolute path" >&2
exit 2
}
echo "$COMMAND" | grep -qE 'rm\s+-rf\s+~' && {
echo "BLOCKED: rm -rf home directory" >&2
exit 2
}
echo "$COMMAND" | grep -qE 'rm\s+-rf\s+\*' && {
echo "BLOCKED: rm -rf wildcard" >&2
exit 2
}
echo "$COMMAND" | grep -qE 'rm\s+-rf\s+\.$' && {
echo "BLOCKED: rm -rf current directory" >&2
exit 2
}
# Block force push to main/master
echo "$COMMAND" | grep -qiE 'git\s+push\s+.*(--force|-f)\s+.*(main|master)' && {
echo "BLOCKED: force push to main/master" >&2
exit 2
}
# Block hard reset to remote
echo "$COMMAND" | grep -qiE 'git\s+reset\s+--hard\s+origin/' && {
echo "BLOCKED: hard reset to remote" >&2
exit 2
}
# Block pipe-to-shell
echo "$COMMAND" | grep -qiE '(curl|wget)\s+.*\|\s*(ba)?sh' && {
echo "BLOCKED: pipe to shell" >&2
exit 2
}
exit 0

84
.claude/hooks/lint-on-save.sh Executable file
View file

@ -0,0 +1,84 @@
#!/bin/sh
# PostToolUse hook: auto-fix lint issues, then report anything remaining
# Uses the project's own make lint-go-incremental (only checks changes since branching from main)
# Runs after formatters (goimports, prettier) so it only sees convention violations
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Need to be in the project root for make targets
PROJECT_DIR=$(echo "$INPUT" | jq -r '.cwd // empty')
if [ -z "$PROJECT_DIR" ]; then
PROJECT_DIR="$CLAUDE_PROJECT_DIR"
fi
if [ -n "$PROJECT_DIR" ]; then
cd "$PROJECT_DIR" || exit 0
fi
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
case "$FILE_PATH" in
*.go)
# Skip third_party (with or without leading path)
case "$FILE_PATH" in
third_party/*|*/third_party/*) exit 0 ;;
esac
# First pass: auto-fix what we can (uses golangci-lint directly for --fix)
PKG_DIR=$(dirname "$FILE_PATH")
if command -v golangci-lint >/dev/null 2>&1; then
golangci-lint run --fix "$PKG_DIR/..." > /dev/null 2>&1
fi
# Second pass: use project's incremental linter (only changes since branching from main)
if [ -f Makefile ] && grep -q "lint-go-incremental" Makefile; then
make lint-go-incremental > "$TMPFILE" 2>&1
elif command -v golangci-lint >/dev/null 2>&1; then
# Fallback if make target isn't available
golangci-lint run "$PKG_DIR/..." > "$TMPFILE" 2>&1
else
exit 0
fi
# Filter out noise (level=warning, command echo, summary) and keep only real violations
# Real violations look like: path/to/file.go:LINE:COL: message (lintername)
VIOLATIONS=$(grep -v "^level=" "$TMPFILE" | grep -v "^\\./" | grep -v "^[0-9]* issues" | grep -v "^$" | grep -E '\.go:[0-9]+:[0-9]+:' | head -20)
if [ -n "$VIOLATIONS" ]; then
echo "$VIOLATIONS" | jq -Rsc --arg fp "$FILE_PATH" \
'{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: ("make lint-go-incremental found issues after editing " + $fp + ":\n" + .)}}'
fi
;;
*.ts|*.tsx)
# Determine eslint binary (prefer local, avoid npx auto-install)
if [ -x ./node_modules/.bin/eslint ]; then
ESLINT="./node_modules/.bin/eslint"
elif command -v npx >/dev/null 2>&1 && npx --no-install eslint --version >/dev/null 2>&1; then
ESLINT="npx --no-install eslint"
else
exit 0
fi
if [ -n "$ESLINT" ]; then
# First pass: auto-fix
$ESLINT --fix "$FILE_PATH" > /dev/null 2>&1
# Second pass: capture remaining issues (include stderr for config/parser errors)
$ESLINT "$FILE_PATH" > "$TMPFILE" 2>&1
if grep -q "error\|warning\|Error:" "$TMPFILE"; then
jq -Rsc --arg fp "$FILE_PATH" \
'{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: ("ESLint found issues after editing " + $fp + ":\n" + .)}}' \
< "$TMPFILE"
fi
fi
;;
esac
exit 0

View file

@ -0,0 +1,23 @@
#!/bin/sh
# PostToolUse hook: run prettier on frontend files after Edit/Write
# Receives tool event JSON on stdin
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
case "$FILE_PATH" in
*.ts|*.tsx|*.scss|*.css|*.js|*.jsx)
# Use local prettier (avoid npx auto-install over network)
if [ -x ./node_modules/.bin/prettier ]; then
./node_modules/.bin/prettier --write "$FILE_PATH" 2>/dev/null
elif command -v npx >/dev/null 2>&1 && npx --no-install prettier --version >/dev/null 2>&1; then
npx --no-install prettier --write "$FILE_PATH" 2>/dev/null
fi
;;
esac
exit 0

82
.claude/lint-on-save.sh Executable file
View file

@ -0,0 +1,82 @@
#!/bin/sh
# PostToolUse hook: auto-fix lint issues, then report anything remaining
# Runs golangci-lint on the affected package (not make lint-go-incremental, which is too
# slow for a PostToolUse hook). Runs after formatters (goimports, prettier) so it only
# sees convention violations.
INPUT=$(cat)
# Extract file_path with grep to avoid jq parse errors from control chars in tool input
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Need to be in the project root for make targets
PROJECT_DIR=$(printf '%s' "$INPUT" | grep -o '"cwd"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"cwd"[[:space:]]*:[[:space:]]*"//;s/"$//')
if [ -z "$PROJECT_DIR" ]; then
PROJECT_DIR="$CLAUDE_PROJECT_DIR"
fi
if [ -n "$PROJECT_DIR" ]; then
cd "$PROJECT_DIR" || exit 0
fi
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
case "$FILE_PATH" in
*.go)
# Skip third_party (with or without leading path)
case "$FILE_PATH" in
third_party/*|*/third_party/*) exit 0 ;;
esac
# First pass: auto-fix what we can (uses golangci-lint directly for --fix)
PKG_DIR=$(dirname "$FILE_PATH")
if command -v golangci-lint >/dev/null 2>&1; then
golangci-lint run --fix "$PKG_DIR/..." > /dev/null 2>&1
fi
# Second pass: lint the affected package (fast) and report remaining issues
if command -v golangci-lint >/dev/null 2>&1; then
golangci-lint run "$PKG_DIR/..." > "$TMPFILE" 2>&1
else
exit 0
fi
# Filter to real violations: path/to/file.go:LINE:COL: message (lintername)
VIOLATIONS=$(grep -E '\.go:[0-9]+:[0-9]+:' "$TMPFILE" | head -20)
if [ -n "$VIOLATIONS" ]; then
echo "$VIOLATIONS" | jq -Rsc --arg fp "$FILE_PATH" \
'{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: ("golangci-lint found issues after editing " + $fp + ":\n" + .)}}'
fi
;;
*.ts|*.tsx)
# Determine eslint binary (prefer local, avoid npx auto-install)
if [ -x ./node_modules/.bin/eslint ]; then
ESLINT="./node_modules/.bin/eslint"
elif command -v npx >/dev/null 2>&1 && npx --no-install eslint --version >/dev/null 2>&1; then
ESLINT="npx --no-install eslint"
else
exit 0
fi
if [ -n "$ESLINT" ]; then
# First pass: auto-fix
$ESLINT --fix "$FILE_PATH" > /dev/null 2>&1
# Second pass: capture remaining issues (include stderr for config/parser errors)
$ESLINT "$FILE_PATH" > "$TMPFILE" 2>&1
if grep -q "error\|warning\|Error:" "$TMPFILE"; then
jq -Rsc --arg fp "$FILE_PATH" \
'{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: ("ESLint found issues after editing " + $fp + ":\n" + .)}}' \
< "$TMPFILE"
fi
fi
;;
esac
exit 0

24
.claude/prettier-frontend.sh Executable file
View file

@ -0,0 +1,24 @@
#!/bin/sh
# PostToolUse hook: run prettier on frontend files after Edit/Write
# Receives tool event JSON on stdin
INPUT=$(cat)
# Extract file_path with grep to avoid jq parse errors from control chars in tool input
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
case "$FILE_PATH" in
*.ts|*.tsx|*.scss|*.css|*.js|*.jsx)
# Use local prettier (avoid npx auto-install over network)
if [ -x ./node_modules/.bin/prettier ]; then
./node_modules/.bin/prettier --write "$FILE_PATH" 2>/dev/null
elif command -v npx >/dev/null 2>&1 && npx --no-install prettier --version >/dev/null 2>&1; then
npx --no-install prettier --write "$FILE_PATH" 2>/dev/null
fi
;;
esac
exit 0

View file

@ -0,0 +1,36 @@
---
paths:
- "server/service/**/*.go"
---
# Fleet API endpoint conventions
These conventions apply when working on API endpoints in the service layer. Not every file in `server/service/` defines endpoints, but the patterns below should be followed whenever you create or modify one.
## Endpoint registration
Register endpoints in `server/service/handler.go`:
```go
ue.POST("/api/_version_/fleet/{resource}", endpointFunc, requestType{})
ue.GET("/api/_version_/fleet/{resource}", endpointFunc, nil)
```
`_version_` is replaced with the actual API version at runtime.
## API versioning
- `ue.EndingAtVersion("v1")` — endpoint only available in v1 and earlier
- `ue.StartingAtVersion("2022-04")` — endpoint available from 2022-04 onward
- Current versions: `v1`, `2022-04`
- New endpoints should use `StartingAtVersion("2022-04")`
## Request body size limits
Use `ue.WithRequestBodySizeLimit(N)` for endpoints accepting large payloads (e.g., bootstrap packages, installers).
## Error response pattern
Return errors in the response body, not as the second return:
```go
return xResponse{Err: err}, nil // correct
return nil, err // WRONG for Fleet endpoints
```
Every response struct needs: `func (r xResponse) Error() error { return r.Err }`
## Reference example
See `server/service/vulnerabilities.go` for a complete example of the request/response/endpoint/service pattern.

View file

@ -0,0 +1,45 @@
---
paths:
- "server/datastore/**/*.go"
---
# Fleet Database Conventions
## Migration Files
- Location: `server/datastore/mysql/migrations/tables/`
- Naming: `YYYYMMDDHHMMSS_CamelCaseName.go` (timestamp + descriptive CamelCase)
- Every migration MUST have a corresponding `_test.go` file
- Structure:
```go
func init() {
MigrationClient.AddMigration(Up_YYYYMMDDHHMMSS, Down_YYYYMMDDHHMMSS)
}
func Up_YYYYMMDDHHMMSS(tx *sql.Tx) error { ... }
func Down_YYYYMMDDHHMMSS(tx *sql.Tx) error { return nil } // always no-op
```
- Test pattern: `applyUpToPrev(t)` → set up data → `applyNext(t, db)` → verify
- Create with: `make migration name=YourChangeName`
## Query Building
- Use `goqu` (github.com/doug-martin/goqu/v9) for SQL query building
- Pattern: `dialect.From(goqu.I("table_name")).Select(...).Where(...)`
- NEVER use string concatenation for SQL — parameterized queries only
- The `gosec` linter checks for SQL concatenation (G202)
## Reader vs Writer
- Reads: `ds.reader(ctx)` — may hit a read replica
- Writes: `ds.writer(ctx)` — always hits the primary
- Using the wrong one causes stale reads or replica lag issues
## Testing
- Integration tests require `MYSQL_TEST=1`: `MYSQL_TEST=1 go test ./server/datastore/mysql/...`
- Use `CreateMySQLDS(t)` helper for test datastore setup
- Table-driven tests with `t.Run` subtests
## Transactions
- Inside `withTx`/`withRetryTxx` callbacks, use the transaction argument — NEVER call `ds.reader(ctx)` or `ds.writer(ctx)` inside a transaction (custom linter rule catches this)
- Same applies to any function that receives a `sqlx.ExtContext` or `sqlx.ExecContext` as an argument — use that argument, not the datastore's reader/writer
## Batch Operations
- Use configurable batch size variables for large operations
- Order key allowlists for user-facing sort fields (prevent SQL injection via ORDER BY)

View file

@ -0,0 +1,90 @@
---
paths:
- "frontend/**/*.ts"
- "frontend/**/*.tsx"
---
# Fleet Frontend Conventions
## Component Structure
Every component should have this 4-file structure:
- `ComponentName.tsx` — Main component
- `_styles.scss` — Component-specific SCSS styles
- `ComponentName.tests.tsx` — Tests
- `index.ts` — Named export
Use the component generator for new components:
```
./frontend/components/generate -n PascalCaseName -p optional/path/to/parent
```
## React Query
- Use `useQuery` for data fetching with `[queryKey, dependency]` and `enabled` option
- Prefer React Query over manual useState/useEffect for API data
- Use `useMutation` for write operations — invalidate related queries on success
- Query key pattern: `["resource", id, teamId]` — include all dependencies
## API Services
- API clients live in `frontend/services/entities/`
- Use `sendRequest(method, path, body?, queryParams?)` from `frontend/services/`
- Endpoint constants in `frontend/utilities/endpoints.ts`
- Build query strings with `buildQueryStringFromParams()` from `frontend/utilities/url/`
- Build full paths with `getPathWithQueryParams(path, params)` — auto-filters undefined/null values
## Permission Checking
Use helpers from `frontend/utilities/permissions/permissions.ts`:
- Global roles: `permissions.isGlobalAdmin(user)`, `isGlobalMaintainer(user)`, `isOnGlobalTeam(user)`
- Team roles: `permissions.isTeamAdmin(user, teamId)`, `isTeamMaintainer(user, teamId)`, `isTeamObserver(user, teamId)`
- Multi-team: `permissions.isAnyTeamAdmin(user)`, `isOnlyObserver(user)`
- License: `permissions.isPremiumTier(config)`, `isFreeTier(config)`
- MDM: `permissions.isMacMdmEnabledAndConfigured(config)`, `isWindowsMdmEnabledAndConfigured(config)`
## Team Context
Use the `useTeamIdParam` hook for team-scoped pages:
- `currentTeamId`: -1 (All teams), 0 (No team), or positive team ID
- `teamIdForApi`: undefined (All teams), 0 (No team), or positive ID — **always use this for API calls**
- `handleTeamChange(newTeamId)` to switch teams
- `isTeamAdmin`, `isTeamMaintainer`, `isObserverPlus` for role checks
## Notifications
- Use `renderFlash(alertType, message)` from `NotificationContext`
- Types: `"success"`, `"error"`, `"warning-filled"`
- Use `renderMultiFlash()` for batch operations
## XSS Prevention
- ALWAYS sanitize user-generated HTML with `DOMPurify.sanitize(html, options)` before `dangerouslySetInnerHTML`
- Configure allowed tags/attributes explicitly: `{ ADD_ATTR: ["target"] }`
## String Utilities
Use helpers from `frontend/utilities/strings/stringUtils.ts`:
- `capitalize(str)`, `capitalizeRole(role)` — handle special casing (Observer+)
- `pluralize(count, singular, pluralSuffix, singularSuffix)` — "1 host" vs "2 hosts"
- `stripQuotes(str)`, `strToBool(str)` — input parsing
- `enforceFleetSentenceCasing(str)` — respects Fleet stylization rules
## Styling (SCSS + BEM)
- Define `const baseClass = "component-name"` at the top of the component
- Elements: `` className={`${baseClass}__element-name`} ``
- Modifiers: `` className={`${baseClass}--modifier`} ``
- Use `classnames()` for conditional classes
- Style files use underscore prefix: `_styles.scss`
## Interfaces & Types
- Interface files live in `frontend/interfaces/` with `I` prefix: `IHost`, `IUser`, `IPack`
- Legacy pattern: some files export both PropTypes (default export) and TypeScript interfaces (named export)
- New code should use TypeScript interfaces only
## Hooks & Context
- Custom hooks in `frontend/hooks/` — e.g., `useTeamIdParam`, `useCheckboxListStateManagement`
- Context providers in `frontend/context/``AppContext` for global state, `NotificationContext` for flash messages
## Terminology
- "Teams" are now called "fleets" in the product. Code still uses `team_id`, `useTeamIdParam`, `permissions.isTeamAdmin`, etc. — don't rename existing APIs, but use "fleet" in new user-facing strings and comments.
- "Queries" are now called "reports." The word "query" now refers solely to a SQL query. Code still uses `useQuery`, `queryKey`, etc. for React Query — that's unrelated to the product terminology change.
## Linting & Formatting
- ESLint: extends airbnb + typescript-eslint + prettier
- Prettier: default config (`.prettierrc.json`)
- `console.log` is allowed (`no-console` is off) — useful for debugging, but clean up before merging
- `react-hooks/exhaustive-deps` is enforced as a warning — include all dependencies in hook dependency arrays
- Run `make lint-js` or `yarn lint` and `npx prettier --check frontend/` before submitting

View file

@ -0,0 +1,105 @@
---
paths:
- "server/**/*.go"
- "cmd/**/*.go"
- "orbit/**/*.go"
- "ee/**/*.go"
- "pkg/**/*.go"
- "tools/**/*.go"
- "client/**/*.go"
- "test/**/*.go"
---
# Fleet Go Backend Conventions
## Error Handling
- Wrap errors with `ctxerr.Wrap(ctx, err, "description")` — never `pkg/errors` or `fmt.Errorf` with `%w`
- For error messages without wrapping, use `errors.New("msg")` not `fmt.Errorf("msg")` (the linter catches this)
- Banned imports: `github.com/pkg/errors`, `github.com/valyala/fastjson`, `github.com/valyala/fasttemplate`
- Use the right error type for the right situation:
- `fleet.NewInvalidArgumentError(field, reason)` — input validation (422). Accumulate with `.Append(field, reason)`, check `.HasErrors()`
- `&fleet.BadRequestError{Message: "..."}` — malformed request (400)
- `fleet.NewAuthFailedError()` / `fleet.NewAuthRequiredError()` — auth failures (401)
- `fleet.NewPermissionError(msg)` — authorized but insufficient role (403)
- Implement `IsNotFound() bool` interface — resource not found. Check with `fleet.IsNotFound(err)`
- `&fleet.ConflictError{Message: "..."}` — duplicate/conflict (409)
- Check error types with: `fleet.IsNotFound(err)`, `fleet.IsAlreadyExists(err)`
## Input Validation
- Validate in service methods, not in endpoint functions
- Accumulate all errors before returning:
```go
invalid := fleet.NewInvalidArgumentError("name", "cannot be empty")
if badCondition {
invalid.Append("email", "must be valid")
}
if invalid.HasErrors() {
return invalid
}
```
## Service Methods
- Signature: `func (svc *Service) MethodName(ctx context.Context, ...) (..., error)`
- Start with authorization: `svc.authz.Authorize(ctx, &fleet.Entity{}, fleet.ActionX)`
- For entity-specific auth, double-authorize: generic check first, load entity, then team-scoped check:
```go
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionRead); err != nil { return nil, err }
host, err := svc.ds.Host(ctx, hostID)
if err != nil { return nil, ctxerr.Wrap(ctx, err, "get host") }
if err := svc.authz.Authorize(ctx, host, fleet.ActionRead); err != nil { return nil, err }
```
- Return errors via ctxerr wrapping
## Viewer Context
- Get current user: `vc, ok := viewer.FromContext(ctx)` — NEVER trust user identity from request body
- Helpers: `vc.UserID()`, `vc.Email()`, `vc.IsLoggedIn()`, `vc.CanPerformActions()`
- System operations: `viewer.NewSystemContext(ctx)` for admin-level automated actions
## Pagination
- Use `fleet.ListOptions` for all list endpoints (Page, PerPage, OrderKey, OrderDirection, MatchQuery, After)
- Return `*fleet.PaginationMetadata` when `IncludeMetadata` is true
- Cursor pagination: check `ListOptions.UsesCursorPagination()`
## Request/Response Pattern
- Request structs: lowercase type, json/url tags: `type listEntitiesRequest struct`
- Response structs: include `Err error` field and `func (r xResponse) Error() error { return r.Err }`
- Endpoint functions: `func xEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error)`
- Errors go in the response body: `return xResponse{Err: err}, nil`
## Logging
- Use slog with context: `logger.InfoContext(ctx, "message", "key", value)`
- NEVER use bare `slog.Debug`, `slog.Info`, `slog.Warn`, `slog.Error` — the `forbidigo` linter rejects these
- NEVER use `print()` or `println()` — use structured logging
## Imports & Utilities
- Internal packages: `github.com/fleetdm/fleet/v4/server/` prefix
- **HTTP clients**: Use `fleethttp.NewClient()` — never `http.Client{}` or `new(http.Client)` directly (custom linter rule)
- **Pointers (Go 1.26+)**: Use `new(expression)` for pointer values: `new("value")`, `new(true)`, `new(yearsSince(born))`. Do NOT use the `server/ptr` package (`ptr.String()`, `ptr.Uint()`, etc.) in new code — it's legacy. You'll see it throughout the existing codebase but should not follow that pattern.
- **Random numbers**: use `math/rand/v2` instead of `math/rand`
- Sets: use `map[T]struct{}`, convert to slice with `slices.Collect(maps.Keys(m))`
- Flexible JSON: use `json.RawMessage` for configs stored as JSON blobs
## Context Utilities
- `ctxdb.RequirePrimary(ctx, true)` — force reads on primary DB (use before read-then-write)
- `ctxdb.BypassCachedMysql(ctx, true)` — disable MySQL cache layer
- `ctxerr.Wrap(ctx, err, "msg")` — ALWAYS use for error wrapping
## Testing
- Use `require` and `assert` from `github.com/stretchr/testify`
- Mock invocation tracking: check `ds.{FuncName}FuncInvoked` bool (auto-set by generated mocks)
- Run `go test ./server/service/` after adding new datastore interface methods — uninitialized mocks crash other tests
- Integration tests need `MYSQL_TEST=1 REDIS_TEST=1`
- Use `t.Context()` instead of `context.Background()`
## Bounded contexts
Some domains use a self-contained bounded context pattern instead of the traditional `fleet/``service/``datastore/` layers:
- `server/activity/` — internal types, mysql, service, API, and bootstrap in one directory
- `server/mdm/` — similar self-contained structure for MDM
When working in these directories, follow the local patterns (internal packages, local types) rather than the top-level Fleet architecture.
## Linting
- Follow `.golangci.yml` — enabled linters: depguard, forbidigo, gosec, gocritic, revive, errcheck, staticcheck
- After editing: `make lint-go-incremental` (only checks changes since branching from main)
- Before committing: `make lint-go` (full lint)

View file

@ -0,0 +1,40 @@
---
paths:
- "orbit/**/*.go"
---
# Fleet Orbit conventions
Orbit is Fleet's lightweight agent that manages osquery, handles updates, and provides device-level functionality. It runs on end-user devices, so reliability and security are critical.
## Architecture
- **Entry point**: `orbit/cmd/orbit/` — main binary
- **Packages**: `orbit/pkg/` — modular packages for each concern
- **Update system**: `orbit/pkg/update/` — TUF-based auto-update for osquery, orbit, and desktop
- **Packaging**: `orbit/pkg/packaging/` — builds installers for macOS (.pkg), Windows (.msi), and Linux (.deb/.rpm)
- **Platform-specific code**: use build tags (`_darwin.go`, `_windows.go`, `_linux.go`) and `_stub.go` for unsupported platforms
## Key patterns
- **Keystore**: `orbit/pkg/keystore/` — platform-specific secure key storage (macOS Keychain, Windows DPAPI, Linux file-based). Always use the keystore abstraction, never raw file I/O for secrets.
- **osquery management**: `orbit/pkg/osquery/` — launching, monitoring, and communicating with osquery. Orbit owns the osquery lifecycle.
- **Token management**: `orbit/pkg/token/` — orbit enrollment token read/write with file locking
- **Platform executables**: `orbit/pkg/execuser/` — run commands as the logged-in user (not root). Critical for UI prompts and desktop app.
## Security considerations
- Orbit runs as root/SYSTEM — every input must be validated
- Never log enrollment tokens, orbit keys, or device identifiers at info level
- File operations on device should use restrictive permissions (0600/0700)
- TUF update verification must never be bypassed
- Use `orbit/pkg/insecure/` only for intentionally insecure test configurations
## Testing
- Unit tests don't need special env vars (no MySQL/Redis)
- Platform-specific tests may need build tags: `go test -tags darwin ./orbit/pkg/...`
- Use `_stub.go` files for cross-platform test compatibility
- Packaging tests may require signing certificates or specific tools (notarytool, WiX)
## Build and packaging
- macOS: `.pkg` built with `pkgbuild`, optional notarization via `notarytool` or `rcodesign`
- Windows: `.msi` built with WiX toolset, templates in `orbit/pkg/packaging/windows_templates.go`
- Linux: `.deb` and `.rpm` via `nfpm`
- Cross-compilation: orbit supports `GOOS`/`GOARCH` targeting

View file

@ -11,13 +11,76 @@
"allow": [
"Read(~/.fleet/claude-projects/**)",
"Write(~/.fleet/claude-projects/**)",
"Edit(~/.fleet/claude-projects/**)"
"Edit(~/.fleet/claude-projects/**)",
"Bash(go test*)",
"Bash(go vet*)",
"Bash(go build*)",
"Bash(go fmt*)",
"Bash(gofmt*)",
"Bash(golangci-lint *)",
"Bash(MYSQL_TEST=1 go test*)",
"Bash(MYSQL_TEST=1 REDIS_TEST=1 go test*)",
"Bash(FLEET_INTEGRATION_TESTS_DISABLE_LOG=1 *)",
"Bash(yarn test*)",
"Bash(yarn lint*)",
"Bash(npx prettier*)",
"Bash(npx eslint*)",
"Bash(npx tsc*)",
"Bash(npx jest*)",
"Bash(make test*)",
"Bash(make lint*)",
"Bash(make build*)",
"Bash(make mock*)",
"Bash(make generate*)",
"Bash(make serve*)",
"Bash(make up*)",
"Bash(make db-*)",
"Bash(make migration*)",
"Bash(make deps*)",
"Bash(make e2e-*)",
"Bash(make run-go-tests*)",
"Bash(make fleet-dev*)",
"Bash(make fleetctl-dev*)",
"Bash(make clean*)",
"Bash(make doc*)",
"Bash(make dump-test-schema*)",
"Bash(make analyze-go*)",
"Bash(make update-go*)",
"Bash(make check-go*)",
"Bash(git status*)",
"Bash(git diff*)",
"Bash(git log*)",
"Bash(git show*)",
"Bash(git branch*)",
"Bash(gh pr *)",
"Bash(gh issue *)",
"Bash(gh run *)",
"Bash(gh api *)"
],
"deny": [
"Bash(git push --force*)",
"Bash(git push -f*)",
"Bash(rm -rf /*)",
"Bash(rm -rf ~*)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard-dangerous-commands.sh",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"if": "Edit(**/*.go) || Write(**/*.go)",
"hooks": [
{
"type": "command",
@ -25,6 +88,28 @@
"timeout": 10
}
]
},
{
"matcher": "Edit|Write",
"if": "Edit(frontend/**) || Write(frontend/**)",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/prettier-frontend.sh",
"timeout": 10
}
]
},
{
"matcher": "Edit|Write",
"if": "Edit(**/*.go) || Edit(**/*.ts) || Edit(**/*.tsx) || Write(**/*.go) || Write(**/*.ts) || Write(**/*.tsx)",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint-on-save.sh",
"timeout": 60
}
]
}
]
}

View file

@ -0,0 +1,58 @@
---
name: bump-migration
description: Bump a database migration's timestamp to the current time. Required when a PR's migration is older than one already merged to main. Use when asked to "bump migration", "update migration timestamp", or when a migration ordering conflict is detected.
allowed-tools: Bash(go run *), Bash(make dump-test-schema*), Bash(git diff*), Bash(ls *), Read, Grep, Glob
model: sonnet
effort: medium
---
# Bump a database migration timestamp
Bump the migration: $ARGUMENTS
## When to use
This is required when a PR has a database migration with a timestamp older than a migration already merged to main. This happens when a PR has been pending merge for a while and another PR got merged with a more recent migration.
## Process
### 1. Identify the migration to bump
If the user provided a filename, use that. Otherwise, find migrations on this branch that are older than the latest on main:
```bash
# List migrations on this branch that aren't on main
git diff origin/main --name-only -- server/datastore/mysql/migrations/tables/
```
### 2. Run the bump tool
The tool lives at `tools/bump-migration/main.go`. Run it from the repo root:
```bash
go run tools/bump-migration/main.go --source-migration YYYYMMDDHHMMSS_MigrationName.go
```
This will:
- Rename the migration file with a new current timestamp
- Rename the test file (if it exists)
- Update all function names inside both files (`Up_OLDTS` → `Up_NEWTS`, `Down_OLDTS``Down_NEWTS`, `TestUp_OLDTS``TestUp_NEWTS`)
### 3. Optionally regenerate the schema
If the migration affects the schema, add `--regen-schema` to also run `make dump-test-schema`:
```bash
go run tools/bump-migration/main.go --source-migration YYYYMMDDHHMMSS_MigrationName.go --regen-schema
```
### 4. Verify
- Check that the old files are gone and new files exist with the updated timestamp
- Verify the function names inside the files match the new timestamp
- Run `go build ./server/datastore/mysql/migrations/...` to check compilation
## Rules
- Always run from the repo root
- Provide the migration filename, not the test filename
- The tool handles both the migration and its test file automatically

View file

@ -1,3 +1,10 @@
---
name: find-related-tests
description: Find test files and functions related to recent git changes. Suggests exact go test commands with correct env vars.
allowed-tools: Bash(git *), Read, Grep, Glob
effort: low
---
Look at my recent git changes (`git diff` and `git diff --cached`) and find all related test files.
For each modified file, find:

View file

@ -1,3 +1,11 @@
---
name: fix-ci
description: Diagnose and fix failing CI tests from a GitHub Actions run. Use when asked to "fix CI", "CI failure", or "failing tests in CI".
allowed-tools: Bash(gh *), Bash(go test *), Bash(go build *), Bash(MYSQL_TEST*), Bash(MYSQL_TEST=1 REDIS_TEST=1 *), Bash(FLEET_INTEGRATION_TESTS_DISABLE_LOG=1 *), Read, Grep, Glob, Edit
model: opus
effort: high
---
Fix failing tests from a CI run. The argument is a GitHub Actions run URL or run ID: $ARGUMENTS
## Step 1: Identify failing jobs

View file

@ -1,3 +1,10 @@
---
name: fleet-gitops
description: Help with Fleet GitOps configuration files including queries, profiles, software, and DDM declarations with validation against upstream references.
allowed-tools: Read, Grep, Glob, Edit, Write, WebFetch, WebSearch
effort: high
---
You are helping with Fleet GitOps configuration files: $ARGUMENTS
Focus on the `it-and-security` folder. Apply the following constraints for all work in this session.

View file

@ -0,0 +1,69 @@
---
name: lint
description: Run linters on recently changed files with the correct tools for each language. Use when asked to "lint", "check style", or "run linters".
allowed-tools: Bash(make lint*), Bash(golangci-lint *), Bash(go vet*), Bash(yarn lint*), Bash(yarn --cwd *), Bash(npx eslint*), Bash(npx prettier*), Bash(git diff*), Bash(git status*), Read, Grep, Glob
effort: low
---
# Lint recent changes
Run the appropriate linters on files changed in the current branch. Use the project's own make targets when available.
## Process
### 1. Detect changed files
Find recently changed files (last commit, staged, and unstaged):
```bash
git diff --name-only HEAD~1 # Last commit
git diff --name-only --cached # Staged but not committed
git diff --name-only # Unstaged changes
```
Combine all three and deduplicate to get the full set.
### 2. Run linters by language
**Go files** (`*.go`):
Use the project's incremental linter — it only checks changes since branching from main:
```bash
make lint-go-incremental
```
This uses `.golangci-incremental.yml` with `--new-from-merge-base=origin/main`. It's faster and more relevant than linting entire packages.
For a full lint (e.g., before committing), use:
```bash
make lint-go
```
**TypeScript/JavaScript files** (`*.ts`, `*.tsx`, `*.js`, `*.jsx`):
```bash
npx eslint frontend/path/to/changed/files
npx prettier --check frontend/path/to/changed/files
```
Or use the make target:
```bash
make lint-js
```
**SCSS files** (`*.scss`):
```bash
npx prettier --check frontend/path/to/changed/files.scss
```
### 3. Report results
For each linter run, show:
- Which packages/files were linted
- Any errors or warnings found
- Suggested fixes (if the linter provides them)
If everything passes, confirm which linters ran and on which files.
If an argument is provided, use it to filter: $ARGUMENTS
- `go` — only Go linters (uses `make lint-go-incremental`)
- `full` — full Go lint (uses `make lint-go`)
- `js` or `frontend` — only frontend linters (uses `make lint-js`)
- A file path — lint that specific file/package

View file

@ -0,0 +1,82 @@
---
name: new-endpoint
description: Scaffold a new Fleet API endpoint with request/response structs, endpoint function, service method, datastore interface, handler registration, and test stubs.
allowed-tools: Read, Write, Edit, Grep, Glob
model: sonnet
effort: high
disable-model-invocation: true
---
# Scaffold a New Fleet API Endpoint
Create a new API endpoint for: $ARGUMENTS
## Process
### 1. Gather Requirements
- Resource name and HTTP method (GET/POST/PATCH/DELETE)
- URL path (e.g., `/api/_version_/fleet/resource`)
- Request body fields (if any)
- Response body fields
- Which API version (use `StartingAtVersion("2022-04")` for new endpoints)
- Does it need a datastore method?
### 2. Read Reference Patterns
Read `server/service/vulnerabilities.go` for the canonical request/response/endpoint pattern:
- Request struct with json tags
- Response struct with `Err error` field and `Error()` method
- Endpoint function with `(ctx, request, svc)` signature
Read `server/service/handler.go` to find where to register the new endpoint.
### 3. Create Request/Response Structs
```go
type myResourceRequest struct {
ID uint `url:"id"`
Name string `json:"name"`
}
type myResourceResponse struct {
Resource *fleet.Resource `json:"resource,omitempty"`
Err error `json:"error,omitempty"`
}
func (r myResourceResponse) Error() error { return r.Err }
```
### 4. Create Endpoint Function
```go
func myResourceEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
req := request.(*myResourceRequest)
result, err := svc.MyResource(ctx, req.ID)
if err != nil {
return myResourceResponse{Err: err}, nil
}
return myResourceResponse{Resource: result}, nil
}
```
### 5. Add Service Interface Method
In `server/fleet/service.go`, add the method to the `Service` interface.
### 6. Implement Service Method
In the appropriate `server/service/*.go` file:
- Start with `svc.authz.Authorize(ctx, &fleet.Entity{}, fleet.ActionRead)`
- Implement business logic
- Wrap errors with `ctxerr.Wrap`
### 7. Add Datastore Interface Method (if needed)
In `server/fleet/datastore.go`, add the method to the `Datastore` interface.
### 8. Register in handler.go
```go
ue.StartingAtVersion("2022-04").GET("/api/_version_/fleet/resource", myResourceEndpoint, myResourceRequest{})
```
### 9. Create Test Stubs
- Unit test with mock datastore in `server/service/*_test.go`
- Integration test stub if it touches the database
### 10. Verify
- Run `go build ./...` to check compilation
- Run `go test ./server/service/` to check mocks are satisfied

View file

@ -0,0 +1,78 @@
---
name: new-migration
description: Create a new Fleet database migration with timestamp naming, Up function, init registration, and test file.
allowed-tools: Bash(date *), Bash(make migration *), Bash(go build *), Bash(go test *), Bash(MYSQL_TEST*), Read, Write, Grep, Glob
model: sonnet
effort: medium
---
# Create a New Database Migration
Create a migration for: $ARGUMENTS
## Process
### 1. Generate Timestamp and Name
Use `make migration name=CamelCaseName` if available, or generate manually:
```bash
date +%Y%m%d%H%M%S
```
The migration name should be descriptive CamelCase (e.g., `AddRecoveryLockAutoRotateAt`, `CreateTableSoftwareInstallers`).
### 2. Create Migration File
Location: `server/datastore/mysql/migrations/tables/{TIMESTAMP}_{Name}.go`
```go
package tables
import "database/sql"
func init() {
MigrationClient.AddMigration(Up_{TIMESTAMP}, Down_{TIMESTAMP})
}
func Up_{TIMESTAMP}(tx *sql.Tx) error {
_, err := tx.Exec(`
-- SQL statement here
`)
return err
}
func Down_{TIMESTAMP}(tx *sql.Tx) error {
return nil
}
```
### 3. Create Test File
Location: `server/datastore/mysql/migrations/tables/{TIMESTAMP}_{Name}_test.go`
```go
package tables
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestUp_{TIMESTAMP}(t *testing.T) {
db := applyUpToPrev(t)
// Set up test data before migration if needed
applyNext(t, db)
// Verify migration applied correctly
// e.g., check table exists, columns added, data migrated
}
```
### 4. Verify
- Run `go build ./server/datastore/mysql/migrations/...` to check compilation
- Run `MYSQL_TEST=1 go test -run TestUp_{TIMESTAMP} ./server/datastore/mysql/migrations/tables/` to test the migration
## Rules
- Every migration MUST have a test file
- Down migrations are always no-ops (`return nil`) — Fleet doesn't use rollback migrations
- Never modify existing migration files — create new ones
- Data migrations go in the `data/` subdirectory

View file

@ -0,0 +1,58 @@
---
name: project
description: Load or initialize a Fleet workstream project context. Use when asked to "load project" or "switch project".
context: fork
allowed-tools: Read, Write, Glob, Grep, Bash(ls *), Bash(pwd *)
effort: medium
---
# Load a workstream project context
## Detect the project directory
Find the Claude Code auto-memory directory for this project. It's based on the working directory path:
1. Run `pwd` to get the current directory.
2. Construct the memory path: `~/.claude/projects/` + the cwd with `/` replaced by `-` and leading `-` (e.g., `/Users/alice/Source/github.com/fleetdm/fleet``~/.claude/projects/-Users-alice-Source-github-com-fleetdm-fleet/memory/`).
3. Verify the directory exists. If not, tell the user and stop.
Use this as the base for all reads and writes below.
## Load the project
Look for a workstream context file named `$ARGUMENTS.md` in the memory directory. This contains background, decisions, and conventions for a specific workstream within Fleet.
If the project context file was found, give a brief summary of what you know and ask what we're working on today.
If the project context file doesn't exist:
1. Tell the user no project named "$ARGUMENTS" was found.
2. List any existing `.md` files in the memory directory so they can see what's available.
3. Ask if they'd like to initialize a new project with that name.
4. If they don't want to initialize, stop here.
5. If they do, ask them to brain-dump everything they know about the workstream — the goal, what areas of the codebase it touches, key decisions, gotchas, anything they've been repeating at the start of each session. A sentence is fine, a paragraph is better. Also offer: "I can also scan your recent session transcripts for relevant context — would you like me to look back through recent chats?"
6. If they want you to scan prior sessions, look at the JSONL transcript files in the Claude project directory (the parent of the memory directory). Read recent ones (last 5-10), skimming for messages related to the workstream. These are large files, so read selectively — check the first few hundred lines of each to gauge relevance before reading more deeply.
7. Using their description, any prior session context, and codebase exploration, find relevant files, patterns, types, and existing implementations related to the workstream.
8. Create the project file in the memory directory using this structure:
```markdown
# Project: $ARGUMENTS
## Background
<!-- What is this workstream about, in the user's words + what you learned -->
## How it works
<!-- Key mechanisms, patterns, and code flow you discovered -->
## Key files
<!-- Important file paths for this workstream, with brief descriptions -->
## Key decisions
<!-- Important architectural or design decisions -->
## Status
<!-- What's done, what remains -->
```
9. Show the user what you wrote and ask if they'd like to adjust anything before continuing.
As you work on a project, update the project file with useful discoveries — gotchas, important file paths, patterns — but not session-specific details.

View file

@ -1,3 +1,12 @@
---
name: review-pr
description: Review a Fleet pull request for correctness, Go idioms, SQL safety, test coverage, and conventions. Use when asked to "review PR" or "review pull request".
context: fork
allowed-tools: Bash(gh *), Read, Grep, Glob
model: opus
effort: high
---
Review the pull request: $ARGUMENTS
Use `gh pr view` and `gh pr diff` to get the full context.

View file

@ -0,0 +1,99 @@
---
name: spec-story
description: Break down a Fleet GitHub story issue into implementable sub-issues with technical specs. Use when asked to "spec", "break down", or "analyze" a story or issue.
allowed-tools: Bash(gh *), Read, Grep, Glob, Write, Edit, WebFetch(domain:github.com), WebFetch(domain:fleetdm.com), WebSearch
model: opus
effort: high
argument-hint: "<issue-number-or-url>"
---
# Spec a Fleet Story
Break down the GitHub story into implementable sub-issues: $ARGUMENTS
## Process
### 1. Understand the Story
- Fetch the issue with `gh issue view <number> --json title,body,labels,milestone,assignees`
- Read the full description, acceptance criteria, and any linked issues
- Identify the user-facing goal and success criteria
- If the issue references Figma designs, API docs, or external specs, fetch them
### 2. Map the Codebase Impact
Search the codebase to understand what exists and what needs to change:
- Find existing implementations of related features (Grep for key terms)
- Identify the tables, service methods, API endpoints, and frontend pages involved
- Check migration files and `server/fleet/datastore.go` for relevant schema
- Trace the request flow: API endpoint → service method → datastore → frontend
### 3. Identify Sub-Issues
Decompose into atomic, implementable units. Each sub-issue should be:
- Completable independently (or with clearly stated dependencies)
- Testable with specific acceptance criteria
- Scoped to one layer when possible (backend, frontend, or migration)
Common decomposition patterns for Fleet:
- **Database migration** — new tables or columns needed
- **Datastore methods** — new or modified query functions
- **Service layer** — business logic, authorization, validation
- **API endpoint** — new or modified HTTP endpoints
- **Frontend page/component** — UI changes
- **fleetctl/GitOps** — CLI and GitOps YAML support
- **Tests** — integration test coverage for the feature
- **Documentation** — REST API docs, user-facing docs
### 4. Write Each Sub-Issue Spec
For each sub-issue, write:
```markdown
## Sub-issue N: [Title]
**Depends on:** [sub-issue numbers, or "none"]
**Layer:** [migration | datastore | service | API | frontend | CLI | docs | tests]
**Estimated scope:** [small: <2h | medium: 2-8h | large: >8h]
### What
[1-3 sentences describing the change]
### Why
[How this contributes to the parent story's goal]
### Technical Approach
- [Specific files to create or modify]
- [Key functions, types, or patterns to follow]
- [Reference existing similar implementations]
### Acceptance Criteria
- [ ] [Testable criterion 1]
- [ ] [Testable criterion 2]
- [ ] [Tests pass: specific test commands]
### Open Questions
- [Any ambiguity that needs product/design input]
```
### 5. Produce the Dependency Graph
Show which sub-issues depend on which:
```
Migration → Datastore → Service → API → Frontend
→ CLI/GitOps
→ Docs
```
Note which sub-issues can be parallelized.
### 6. Write the Output
Create a spec document with:
1. **Summary** — one paragraph overview
2. **Sub-issues** — each with the template above
3. **Dependency graph** — visual ordering
4. **Open questions** — anything that needs clarification before implementation begins
5. **Suggested PR strategy** — single PR vs multiple, review order
## Rules
- Every sub-issue must reference specific files and patterns from the codebase
- No vague specs: "implement the backend" is not a sub-issue
- If you find ambiguity in the story, flag it as an open question rather than guessing
- Check for related existing issues with `gh issue list --search "keyword" --limit 10`
- Consider Fleet's multi-platform nature: does this affect macOS, Windows, Linux, iOS, Android?
- Consider enterprise vs core: does this need license checks?

View file

@ -0,0 +1,31 @@
---
name: test
description: Run tests related to recent changes with appropriate tools and environment variables. Use when asked to "run tests", "test my changes", or "test this".
allowed-tools: Bash(go test *), Bash(MYSQL_TEST*), Bash(MYSQL_TEST=1 *), Bash(MYSQL_TEST=1 REDIS_TEST=1 *), Bash(FLEET_INTEGRATION_TESTS_DISABLE_LOG=1 *), Bash(yarn test*), Bash(npx jest*), Bash(git diff*), Bash(git status*), Read, Grep, Glob
effort: low
---
Run tests related to my recent changes. Look at `git diff` and `git diff --cached` to determine which files were modified.
## Go tests
For each modified Go package, run the tests with appropriate env vars:
- If the package is under `server/datastore/mysql`: use `MYSQL_TEST=1`
- If the package is under `server/service`: use `MYSQL_TEST=1 REDIS_TEST=1`
- Otherwise: run without special env vars
## Frontend tests
If any files under `frontend/` were modified, run the relevant frontend tests:
- Find test files matching the changed components (e.g., `ComponentName.tests.tsx`)
- Run with: `yarn test --testPathPattern "path/to/changed/component"`
- If many files changed, run the full suite: `yarn test`
## Choosing what to run
- If only Go files changed, run Go tests only
- If only frontend files changed, run frontend tests only
- If both changed, run both
- If an argument is provided, use it as a filter: $ARGUMENTS (passed as `-run` for Go or `--testPathPattern` for frontend)
Show a summary of results: which packages/suites passed, which failed, and any failure details.