# Personal data (user fills these)
cv.md
article-digest.md
local/
# Everything under data/ is generated or personal at runtime — only .gitkeep
# placeholders are tracked (verify with `git ls-files data/`). A blanket rule
# means a new generated file (e.g. a future log/cache addition) is covered
# for every user automatically, with no per-filename .gitignore update needed.
data/*
!data/.gitkeep
!data/offers/
!data/parser-output/
# Received offers/contracts, promise notes, prep reports (PII — never commit)
data/offers/*   
!data/offers/.gitkeep
data/parser-output/**
!data/parser-output/**/
!data/parser-output/.gitkeep
!data/parser-output/**/.gitkeep
reports/*.md
!reports/.gitkeep
output/*
!output/.gitkeep
batch/logs/*
!batch/logs/.gitkeep
batch/batch-state.tsv
batch/batch-state-recovery.d/
batch/batch-input.tsv
batch/tracker-additions/**/*.tsv
!batch/tracker-additions/.gitkeep
jds/*
!jds/.gitkeep

# Personal documents — never commit scans or copies of these (PII).
# Catches common identity/credential filenames so they can't be staged by accident.
*passport*
*diploma*
*简历*
*学位*
*成绩单*

# Interview prep (accumulated STAR+R stories — user data)
interview-prep/*
!interview-prep/
!interview-prep/.gitkeep
# Session transcripts hold real interviewer names/companies — ignore content,
# ship only the system-owned scaffold (README + .gitkeep). #1242
!interview-prep/sessions/
interview-prep/sessions/*
!interview-prep/sessions/.gitkeep
!interview-prep/sessions/README.md

# Writing samples (user's personal files — never committed)
writing-samples/*
!writing-samples/README.md

# User config and customization (never auto-updated)
config/profile.yml
config/cv-facts.json
config/benchmarks.yml
# Fork-local path declarations — this checkout's own files, read by
# update-system.mjs. Gitignored on purpose: it describes THIS clone, not the
# project, and must survive the sync that overwrites update-system.mjs (#2421).
config/local-paths.txt
portals.yml
modes/_profile.md
modes/_custom.md
modes/_brief.md
voice-dna.md
.update-dismissed
.update-lock
.manifesto-noted

# Plugin layer — opt-in toggles + user-installed plugins + integrity lock (never auto-updated)
config/plugins.yml
plugins.local/
plugins.lock

# Secrets (never commit — use .env.example as template)
.env
# Universal safety net — protects ALL checkouts/branches, incl. untracked subprojects
# (e.g. web/ before it graduates to main). Never commit local env files or TS build caches.
**/.env.local
**/.env*.local
**/*.tsbuildinfo

# Generated
.resolved-prompt-*
node_modules/
bun.lock
# Working copy the test suite makes under the repo root (test-all.mjs
# `mkdtempSync(join(ROOT, '.tmp-script-test-'))`). A clean run deletes it; a
# run interrupted mid-way leaves ~650MB of duplicated repo behind, which a
# reflexive `git add .` would then stage as ~1000 new files.
.tmp-script-test-*/

# Report-number reservation sentinels (ephemeral, created by reserve-report-num.mjs)
reports/*-RESERVED.md

# Backup files. Two shapes exist: `{file}.bak` (normalize-statuses.mjs,
# dedup-tracker.mjs, tracker.mjs) and `{file}.bak-{ISO timestamp}`
# (web/src/lib/core/safe-write.ts). A plain `*.bak` glob misses the timestamped
# form, which for cv.md or portals.yml leaves a file holding the same PII as the
# original sitting untracked in the repo root. Trailing `*` covers both.
*.bak*

# The tracker and its follow-ups file in the LEGACY ROOT LAYOUT. resolveTrackerPath()
# documents its own fallback chain — CAREER_OPS_TRACKER > <root>/data/applications.md
# > <root>/applications.md — and that third entry is a supported install shape, not a
# mistake. data/ is covered by the blanket rule at the top of this file; the root
# spelling was not covered by anything.
#
# The content is the user's whole job search: company, role, score, status, notes.
# cv.md, portals.yml, article-digest.md and voice-dna.md are all ignored at the root
# already, and #3469 ignores the derived .db index on the argument that it "holds the
# same PII as the tracker" — while the tracker itself sat beside it, unignored.
#
# Unanchored, like the .db rule below and for the same reason: getCareerOpsRoot()
# resolves a relative CAREER_OPS_ROOT (or .career-ops-data marker) against the
# codebase directory, so a data root configured as `career-data` puts these inside
# the checkout, where an anchored rule would not reach them. The upgrade fixtures
# that share these names stay tracked via the `!test-fixtures/**` rule further down,
# which is a later pattern and therefore wins.
applications.md
follow-ups.md

# The tracker's derived SQLite index (tracker.mjs, #918). It sits beside the
# tracker markdown, so on the standard layout it lands in data/ and is already
# covered by the rules above — but resolveTrackerPath() falls back to
# `<root>/applications.md` when data/applications.md is absent, and the index
# follows it there. That is the legacy install layout, and it is also every
# fresh clone: `node test-all.mjs` leaves a 36KB applications.db in the repo
# root, which `git add .` then stages.
#
# Its content is the whole tracker — company, role, score, status, notes — so it
# carries the same PII as the file it indexes, under a name none of the rules
# above match. SQLite's -wal/-shm sidecars travel with it.
#
# Deliberately not root-anchored. The index follows the tracker markdown, and
# getCareerOpsRoot() resolves a relative CAREER_OPS_ROOT (or .career-ops-data
# marker) against the codebase directory — so a data root configured as
# `career-data` puts both inside the checkout, in neither of the two places an
# anchored rule would cover. Nothing tracked in this repo ends in .db, and an
# ignore rule cannot untrack a file that is already tracked, so the cost of the
# wider glob is a `git add -f` on a .db fixture nobody has added yet; the cost
# of the narrower one is this leak, for every non-default data root.
*.db
*.db-wal
*.db-shm

# OpenRouter runner — generated data (personal, never commit)
data/model-blacklist.json

# OS
.DS_Store
*.mov
*.mp4

# IDE-specific local config
.idea/
.vscode/

# CLI-specific local config
.claude/settings.local.json
.claude/memory/
.opencode/settings.local.json
.opencode/memory/
# OpenCode project config (often holds personal MCP/model/per-machine prefs).
opencode.json
# Claude Code project-level MCP server config — commonly ends up holding
# personal server credentials (e.g. a plugin's API token) once a user adds
# an MCP server, same risk class as .env.
.mcp.json
career-dashboard
career-dashboard.exe
package-lock.json
!web/package-lock.json

# Playwright MCP server run artifacts (ephemeral, machine-local)
.playwright-mcp/
test-results/
playwright-report/

# Planning (local scratchpad)
plan/

# Web — runtime cache/history the dashboard writes locally; nothing here is tracked.
.career-ops-web/

# Upgrade-test fixtures are fictional system-layer data, never user data
# (see DATA_CONTRACT.md). Re-include them past the unanchored user-layer
# patterns above (cv.md, portals.yml).
!test-fixtures/**

# Profile intake sources (master CV / LinkedIn export / diplomas / references
# — user PII, never commit). Ship only the system-owned scaffold. #1723
documents/*
!documents/.gitkeep
!documents/README.md
# Intake fingerprints — the recorded source filenames may themselves carry
# PII (e.g. cv/jane-doe-master-cv.pdf). #1723
data/intake-state.json
