__pycache__/
*.py[cod]
*.egg-info/
dist/
build/
# …but the vendored pdf.js ships a `build/` dir (pdf.mjs + worker) that
# must be tracked — the `build/` rule above would otherwise drop it.
!src/precis_web/static/pdfjs/build/
.mypy_cache/
.ruff_cache/
.pytest_cache/
.venv/
.zsh_history

# secrets
.env
.env.*
!.env.example

# The LIVE per-cluster ansible overlay (real Tailscale/LAN IPs, node
# hostnames, the encrypted vault) is LOCAL-ONLY — never committed into this
# PUBLIC repo (a public push is irreversible). The operator symlinks/clones
# their private cluster inventory here; only the scrubbed template
# `deploy/inventory.example/` is tracked. Guarded in depth by
# tests/test_deploy_tree_no_secrets.py (the ship-gate leak-gate).
deploy/inventory/
deploy/inventory
deploy/.vault-pass
deploy/*.local.yml

# OS junk
.DS_Store
**/.DS_Store

# editor swap files
*.swp
*.swo

# audio-feed output artifact (regenerated; not source)
feed.xml

# guide/index.html is generated by guide/build.py from the tour manifests +
# narration + guide/assets/ — the Pages workflow rebuilds it on every push,
# so a committed copy would only churn/drift. guide/assets/ (curated,
# human-reviewed captures + narration mp3s) IS source and stays tracked.
# guide/README.md is ALSO generated by guide/build.py (build_markdown), but
# unlike index.html it IS committed — it's the repo-rendered guide (GitHub
# shows it inline, no Pages dependency). Regenerate + commit it after any
# narration/capture change; tests/test_guide_scripts.py tripwires drift.
guide/index.html
# guide/guide.mp4 is assembled by scripts/guide-video from the committed
# assets; its canonical home is YouTube (upload is manual), so the large
# binary stays out of git — anyone can reproduce it from a clean tree.
guide/guide.mp4

# usability harness output
scripts/exercise-mcp/out/

# pytest junit XML — per-run artifact, regenerated by the test
# command and not useful in version control.
.test-results.xml

# Board render kept for inspection: PRECIS_PCB_RENDER_OUT=... on
# tests/test_pcb_fab_render_all_layers.py writes the fab SVG here. Half a
# megabyte, regenerated on demand, and stale the moment the engine moves.
board*.svg

# devin/windsurf: ignore session state, but TRACK the shareable
# workflows (ported from .claude/commands/) so they ship with the repo.
.devin/*
!.devin/workflows/
**/.windsurf

# Claude Code: ignore session state (worktrees, transcripts, todos,
# settings.local.json), but TRACK the shareable config so slash commands +
# project settings ship with the repo and reach every worktree. New files
# under .claude/commands/ are now tracked by default — no more `git add -f`.
.claude/*
!.claude/commands/
!.claude/settings.json
!.claude/skills/
!.claude/agents/

# Session-liveness anchor: an untracked marker a live session drops at a
# worktree root so the auto-reaper won't cull it mid-session. Never source.
.keep-session-alive

# Personal ops notes — anything matching *.local.md / *.local/.
# Convention for ops crib sheets, scratch SQL, host-specific quirks.
*.local.md
*.local/
tmp_errors

# CAD example export artifacts are regenerated by examples/cad/generate.py
examples/cad/out/

# Claude Code auto-memory store — session-local, not source.
memory/

# Fixer loop runtime scratch (ADR 0048): build worktrees, lockfile, fixer.log.
# Never source — and `scripts/ship`'s `git add -A` must not commit it.
.fixer-work/

# testmon test-selection DB (volatile binary; shared via TESTMON_DATAFILE, not git).
# Glob covers the SQLite WAL sidecars too — `.testmondata-shm` / `.testmondata-wal`
# use a HYPHEN, which the old `.testmondata.*` (dot) missed, so `git add -A` swept
# a 4.7MB `-wal` blob into a ship once. `.testmondata*` catches all of them.
.testmondata*
# Gate coverage artifacts (scripts/ship full path): coverage.xml feeds the
# host-side diff-cover gate, .coverage* (sqlite + per-process shards) feeds
# scripts/mutate-diff's covering-test lookup. Regenerated every full gate —
# never shipped.
coverage.xml
.coverage
.coverage.*
# scripts/mutate-diff's scratch diff patch — regenerated per run, not source.
.mutate-diff.patch

# scripts/nightly local full-build result (machine-specific state, not git)
.nightly-status.md

# scripts/deploy success marker (`<sha> <epoch>` of what's actually running on
# the cluster) — machine-local state scripts/ship reads to report deploy lag.
.deploy-state

# scripts/deploy per-run logs (full ansible output incl. the profile_tasks
# timing table, for later analysis). Deliberately OUTSIDE deploy/ — the logs
# name real hosts, and tests/test_deploy_tree_no_secrets.py walks that tree.
.deploy-logs/

# rtk: keep project filters, ignore runtime tee logs/cache
.rtk/*
!.rtk/filters.toml

# code-index: indexer deps (cached, reproducible via scripts/code-index)
scripts/code-search/node_modules/

# generated doc indexes: regenerated per-worktree by scripts/docs-index
# (SessionStart hook) — see docs/README.md §Generated indexes
docs/backlog/INDEX.md
docs/runbooks/INDEX.md
docs/codebase-map.md

# per-session ship/deploy/mutate logs redirected by an operator (never commit)
.ship.log
.deploy-run.log
.mutate-run.log

# scripts/ship's gated-sha pin: the exact sha a FULL gate validated, so /go can
# deploy THAT tree instead of re-resolving the branch name `main` (which a
# sibling --quick ship may have moved meanwhile). Per-worktree, rewritten every
# ship — state, not source.
.ship-sha
