# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Exception: a vendored GitHub Action's compiled output belongs in the repo, not a build artifact.
# Without this, `dist/` above silently drops .github/actions/cla-assistant-lite/dist/index.js from
# every `git add`, which would ship a workflow that references a file that was never committed.
!/.github/actions/**/dist/
!/.github/actions/**/dist/**

# Engine runtime data (PHI may live here — never commit)
*.db
*.db-wal
*.db-shm
*.pid
out/
harness_io/
*.log

# One-time bootstrap admin credential written next to the store (consumed + deleted by the operator) — never commit
bootstrap-admin.txt

# Secrets / credentials — never commit (no such files exist today; these are foot-gun guards)
.env
.env.*
*.key
*.pem
*.pfx
secrets/

# Container deployment secrets + TLS material — never commit (the *.example templates ARE tracked)
/docker/secrets.env
/docker/tls/

# are never committed. Ignore every samples/ subdirectory by DEFAULT rather than naming the partner
# here — fail-closed, so a new unlisted staging dir cannot reach a public repo just because nobody
# remembered to add a rule. The shipped example dirs are re-included explicitly.
/samples/*/
!/samples/config/
!/samples/consistency/
!/samples/dicom/
!/samples/ech-sidecar/
!/samples/generators/
!/samples/messages/
!/samples/results_relay/
# Generated, regenerable test corpus (build with: python -m messagefoundry.generators.adt). MUST come
# after the un-ignores above, or the re-included parent would drag it back in.
/samples/messages/adt/
# Workflow agent self-verification temp dirs (an isolated copy of a ported migration module + codesets
# + fixture for a check/dryrun). Customer-estate-derived — never commit. Delete after a porting batch.
/_verify_*/
# exports). Customer data — NEVER commit. `git archive` (the publish path) excludes it by
# construction; this is the local-only working area.
/migration-local/

# Tooling
.mypy_cache/
.ruff_cache/
.pytest_cache/
.vscode/
.idea/

# CI-only quality artifacts (generated by .github/workflows/quality-advisory.yml, and by anyone
# reproducing those jobs locally). None of these were ignored before, which mattered: the C901 delta
# inputs are ruff JSON carrying ABSOLUTE local paths, so a local `git add -A` would stage a home
# directory path straight into a commit -- exactly what scripts/security/scan_forbidden.py fires on.
coverage.xml
.coverage
diff-cover.md
jscpd-report/
.mutmut-cache
mutants/
mutmut-run.txt
mutmut-results.txt
c901-head.json
c901-base.json
base-tree/
# DAST scan receipts (ADR 0155). The documented verification commands write these into the repo root,
# and committed scan output is a class this project does not produce: the receipt carries no body,
# header or token, but it is still a machine-readable inventory of which routes are ungated and which
# are unreachable. CI uploads them as artifacts instead.
dast-auth-receipt.json
canary-*.json

# Claude Code: settings.json is shared/tracked; settings.local.json is machine-local (never commit).
# That first clause was FALSE from the day `/.claude/` landed until the publishing-boundary block
# below was reshaped to `/.claude/*` plus a negation -- BACKLOG #327's DONE note carried the
# contradiction to the owner rather than editing it in that lane. It is true again now. The rule
# below is redundant with `/.claude/*` and is kept as the statement of intent for this one file.
.claude/settings.local.json

# Local reference notes pointing at machine-specific Claude Code transcript paths — never commit
TRANSCRIPTS.md

# Local planning/marketing working docs — keep out of the repo
# (docs/DUAL_LICENSING_PLAN.md is now a committed artifact — see ADR 0017 / #13 licensing)
/docs/marketing/
# Individual docs/security/* working files (and vuln_metrics.py's local CSV output) were once
# enumerated here. They are all covered by the blanket /docs/security/ rule further down, so the
# list was redundant — and enumerating private filenames in a public file discloses the shape of
# the private set for no gain. Do not re-add per-file entries; extend the blanket rule instead.

# Operator-local load/throughput profiles are NOT ignored here any more, and must never be again.
# Two were, by name, at /harness/load/profiles/<name>.toml. That is a contradiction git cannot
# report: hatchling's force-include walks the FILESYSTEM and reads no .gitignore, so a harness wheel
# built on the machine holding those files shipped them, hosts and ports included (BACKLOG #1835).
# They belong in migration-local/profiles/, which /migration-local/ above already covers, which no
# force-include source names, and which docs/LOAD-TESTING.md and harness/load/profiles/README.md
# already name as the home for site-specific numbers. harness/load/profile.py reads that directory,
# so `--load <name>` still resolves. tests/test_packaging.py refuses a new anchored entry inside a
# force-included tree.
# Deep-research survey of alternative OSS DB backends for the shared-DB commit-wall — local working
# note, not a committed artifact (the conclusion is carried in the throughput ADRs / AI memory).
/docs/research/db-commit-wall-backend-survey.md

# --- MessageFoundry ---
.mefor/
*.db*
.DS_Store
Thumbs.db

# Agent/web-fetch debris dropped in the repo ROOT (e.g. blog.html, contest.txt, free.html) — never
# commit. Anchored to root (leading /) so tracked *.html/*.txt UNDER subdirs (docs/, samples/,
# scripts/, packaging/, messagefoundry/auth/data/…) are unaffected; no *.html/*.txt is legitimately
# tracked at the repo root, so an anchored catch-all is safe. Force-add (git add -f) if one ever is.
/*.html
/*.txt
/*.yml

# The real customer/vendor token list consumed by scripts/security/scan_forbidden.py at
# commit time. Only the SYNTHETIC scan-tokens.local.txt.example is ever committed.
#
# Deliberately unconditional, and listed ahead of the scanner it serves: an operator creates
# this file before the tooling that references it exists, so ignoring the path up front
# removes any window in which a real token list sits un-ignored in a working tree.
scripts/security/scan-tokens.local.txt

# --- Private-only paths (formerly enforced by scripts/publish/publish-denylist.txt) ---------------
# The cutover RETIRES the publish deny-list. On the private repo these were TRACKED and stripped from
# the mirror at publish time; with the deny-list gone and development happening directly on the public
# repo, a gitignore rule is now the ONLY thing keeping them out of a commit -- and the cutover runbook
# runs `git add -A`. Same failure shape as the leak-scanner token file, different files.
#
# DELIBERATELY NOT LISTED: tests/test_scan_forbidden.py, tests/test_anon_core.py and
# .github/dependabot.yml. Those were deny-listed too, but this change set publishes synthetic/public
# versions of them -- ignoring them here would silently drop content that is meant to ship.
# PUBLISHED by this change (removed from the list above): CLAUDE.md, docs/BACKLOG.md and
# docs/WORKTREES.md. All three were scanned and carry no customer/estate tokens (BACKLOG.md needed a
# two-line redaction). CLAUDE.md in particular MUST be tracked: it is gitignored-by-default's worst
# case here, because `git worktree add` cannot deliver an untracked file, so every worktree silently
# came up with ZERO project conventions loaded.
#
# `.claude/` is ignored by CONTENTS (`/.claude/*`), NOT as a directory, so `settings.json` can be
# re-included on the next line. Ignoring the directory itself would make that negation a silent
# no-op: git cannot re-include a file whose parent directory is excluded. Everything else under
# `.claude/` stays ignored by the same star rule -- `settings.local.json` (machine-local, also
# named at line 85), `worktrees/` (session state, not configuration), and anything Claude Code
# adds there later, which is ignored by DEFAULT rather than by enumeration.
#
# WHY settings.json IS TRACKED. It carries ENFORCED controls -- the deny-list covering `.env`,
# `secrets/**`, keys and the local `*.db` store -- and settings are enforced by the client where
# CLAUDE.md is only context, so section 5's prose is not a substitute for it. It does NOT wire
# `block-blanket-git-stage.ps1`: that script is present and tested but referenced by no matcher
# (BACKLOG #1339). Untracked, it hit the exact CLAUDE.md failure described above:
# measured 2026-08-13, of 62 local checkouts carrying CLAUDE.md only 12 had `.claude/settings.json`,
# so 50 ran with no deny-list and no staging guard. `git worktree add` delivers tracked files only.
/.claude/*
!/.claude/settings.json
# Named explicitly rather than left to `/.claude/*` above. Until this line, the only travelling
# protection for the session tree was that wildcard -- `.git/info/exclude` carries it too, but that
# file is per-clone and reaches nobody else. A later edit that narrows the wildcard or adds a second
# negation would expose full nested checkouts carrying `.venv`, `messagefoundry.db` and the caches.
# NOTHING under `.claude/` may be negated except `settings.json`; tests/test_private_paths_stay_ignored.py
# pins the tracked set so widening it fails a required check instead of shipping.
/.claude/worktrees/

# Codex transport is tracked; session state and migration backups are local.
/.codex/sessions/
/.codex/migration-backup/
/TRANSCRIPTS.md
/docs/security/
/docs/reviews/
/docs/marketing/
# Deliberately still private, each for a different reason. What decides which side a security
# document lands on is stated publicly in docs/SECURITY-DOCS-POLICY.md — do not restate the
# reasoning, the inventory or the file count here:
#   docs/security/      — maintainer-internal working documents; see the policy above.
#   docs/reviews/       — point-in-time review findings, withheld on the same test.
#   docs/CI-TOPOLOGY.md — STALE: it documents the retired private-repo/public-mirror split and
#                         scripts/publish/, which the cutover deleted. Publishing it would actively
#                         mislead. Rewrite for the post-cutover topology or delete it.
#   (docs/Secure_Development_Standards.md was here. PUBLISHED 2026-07-29 on the owner's call: the
#    document licenses itself as publishable and adopter-facing, messagefoundry.org already served
#    it, and 44 citations across the repo — including adr/TEMPLATE.md — pointed at a file that did
#    not exist. Published from the newest copy, de-branded back to the generic lineage its own
#    revision history names.)
/docs/CI-TOPOLOGY.md

# --- Development-process material (ADR 0160 Phase 1) ----------------------------------------------
# Subject test, from ADR 0160 D1: a tracked file must be something an OPERATOR running MessageFoundry
# needs, or something a SECURITY REVIEWER assessing it needs. Material whose subject is how this
# project is DEVELOPED -- session orchestration, wave schedules, per-session briefs -- fails both.
#
# Custody moved to the vault (MEFORORG/MessageFoundry-vault) BEFORE these left the tracked tree, so
# this rule is not the only copy. That ordering is the point: gitignoring alone would have left 103
# files as single unversioned copies that `git clean -xdf` erases.
#
# NOT a confidentiality control, and must never be described as one -- `git log` still contains all
# of it, and none of it was ever secret. It is process noise, not sensitive content. Anything
# genuinely sensitive belongs in the vault and always did (see the blanket rules above).
#
# HANDOFF-*.md is anchored to the ROOT and deliberately a PATTERN rather than the two filenames it
# currently matches: session handoffs kept landing in the repo's front door, and a pattern fails
# closed on the next one instead of waiting for somebody to notice. A handoff that genuinely must
# ship can still be force-added (`git add -f`).
#
# ⛔ DO NOT "FIX" THE ANCHOR BY DROPPING THE LEADING SLASH. It looks like a gap -- the root-anchored
# form matches nothing under docs/ -- and it was proposed as a tightening on exactly that reasoning.
# It is the wrong fix twice over:
#   * The two places a handoff must not land are the repo ROOT and docs/releases/, and BOTH are
#     already covered -- the root by the rule below, the directory by the rule above it. The case
#     that motivated the change (PR #225 landing a handoff in docs/releases/ while the Phase 1 PR was
#     open) is closed by that directory rule the moment nothing is tracked there, which is now true.
#   * An unanchored `HANDOFF-*.md` would fail closed on `docs/benchmarks/`, where 21 handoff
#     documents are TRACKED ON PURPOSE: they carry the measurement narrative for the ~133 data files
#     beside them, and a new benchmark run legitimately produces another. Silently ignoring those
#     would break working practice to close a gap that is already shut.
/docs/releases/
/HANDOFF-*.md

# --- Business material and internal engineering records (ADR 0160 D1, 2026-08-31) -----------------
# Same D1 subject test as the block above, applied to two sets Phase 1 did not reach. Owner ruling
# 2026-08-31 authorised this pass and settled the boundary question that came with it: D1 category 2
# GOVERNS, so anything a security reviewer needs STAYS PUBLIC. That is why no security document is
# in this block -- SECURITY.md, PHI.md, SUPPLY-CHAIN.md, SECURITY-LOOSENING.md, the ASVS phase
# inventory and the Secure_* standards are all reviewer material and remain tracked.
#
# Custody moved to the vault BEFORE these left the tracked tree, same ordering and same reason as
# Phase 1: 96b8feec on MEFORORG/MessageFoundry-vault, branch lander/adr0160-custody. This block
# untracks 46 files. Of those 46, 15 already existed in the vault and 7 of the 15 were DIVERGENT
# stale forks predating the 2026-07-27 cutover, so the vault copies were overwritten with the
# maintained public ones. A bare untrack would have left the vault holding the older text and
# nothing holding the newer.
#
# An earlier draft of this block said "20 of the 51 files ... and 9 of those". Those figures are
# measured over the CUSTODY set, which is larger: 96b8feec also took custody of docs/design and of
# docs/testing/VERIFY.md, and all of those stay tracked here for the reasons recorded below. 46 is
# the number that left, and 15/7 are its figures. The 20/9 pair is recorded rather than deleted
# because it also reached the pull request description, so a reader who remembers it needs to see
# it named as describing a different set rather than silently replaced.
#
# NOT a confidentiality control, and must never be described as one. `git log` still contains all of
# it and none of it was ever secret; the owner ruled explicitly against a history rewrite on
# 2026-08-31. This is subject-matter tidying, not withholding.
#
# Business and legal working documents -- audience is the maintainer deciding project direction, not
# an operator running the engine or a reviewer assessing it.
/docs/BRAND.md
/docs/CONTRIBUTOR-FIRST-ISSUES.md
/docs/CONTRIBUTOR-PROGRAM-PLAN.md
/docs/COUNSEL-ENGAGEMENT-BRIEF.md
/docs/DUAL_LICENSING_PLAN.md
/docs/POSITIONING.md
# Internal engineering records. research is exploratory; archive/throughput is superseded plans whose
# shipped outcome is docs/THROUGHPUT.md.
/docs/research/
/docs/archive/throughput/
#
# docs/design/ IS NOT HERE, and it was in an earlier draft of this block. Measured before removing it:
# docs/design/freethread.md is a CLAIM FILE for the required-status-check drift guard
# (tests/test_required_contexts.py reads it, and treats a missing claim file as an ERROR rather than a
# skip -- deliberately, because silently dropping a claim file is how a drift guard comes to guard
# nothing). Untracking it turns that guard's own anti-narrowing assertion red. The other three design
# notes have no such reference, but carving one file out of four costs a second negation and a second
# pinned exception to save moving three exploratory documents. Not worth it. The tree stays.
#
# docs/testing is maintainer QA planning -- draft plans awaiting owner approval, and a matrix and
# plan written against ONE specific maintainer build box. An operator reads none of it.
#
# An earlier draft of this line said WIN2025-TEST-PLAN.md "names the host and its identity". It does
# NOT: the file uses <box-hostname>, <box-ip>, <server>, <u> and <p> placeholders throughout. The
# removal is right on the D1 subject test -- maintainer QA planning for one build box is neither
# operator nor security-reviewer material -- and it never needed a disclosure claim to justify it.
# Overstating the reason is how a correct decision acquires a wrong rationale that outlives it.
#
# ONE EXCEPTION, and it is why this is a contents-glob rather than a directory rule.
# docs/testing/VERIFY.md is OPERATOR material and stays tracked: it documents `messagefoundry verify`,
# the wheel-only on-box acceptance check a real deployment runs, and docs/README.md lists it as step 6
# of "Start here -- a new operator, in order" while line 8 warns in terms that it "is an operator tool,
# not a test plan". docs/SECURITY.md cites it too. It passes D1 category 1 outright.
#
# `/docs/testing/` as a DIRECTORY rule would make the negation below inert -- git does not descend
# into an excluded directory, so a file inside one cannot be re-included. The trailing `*` keeps the
# directory itself readable so the negation binds. Do not "simplify" it back.
/docs/testing/*
!/docs/testing/VERIFY.md
# docs/testing/README.md is the SECOND negation, and it is a marker rather than QA material.
#
# The rule above works, and its success is the problem: from an engine checkout the directory holds
# one file, so a path cited under it is INDISTINGUISHABLE from work that was never done. A reader
# greps a cited path, finds nothing, and concludes the document does not exist -- when it does, in
# the vault. Measured 2026-09-03: BACKLOG #1011 step 6 instructed a reader to mark a disposition
# discharged in a path under this tree and called that file "the only place in the repo that records
# the true state", a sentence that is false from any engine checkout because the file is not in the
# repo at all. A builder acting on it would have claimed a criterion it could never read.
#
# The marker is the checkout-side answer: standing in the directory, or browsing to it on GitHub,
# you are told the material moved and told not to read absence as evidence. It is a POINTER, never
# an index -- it names no vaulted document, because a path-to-document map over a closed set
# discloses what is NOT covered by subtraction. Keep it that way if you edit it.
#
# This negation and the VERIFY.md one are the ONLY two permitted here, and
# tests/test_private_paths_stay_ignored.py pins the pair as an exact set. A third fails there until
# someone writes down why. See BACKLOG #1435.
!/docs/testing/README.md
# DO NOT ADD docs/benchmarks/ OR docs/quality-gates/ HERE. Both are DELIBERATELY ABSENT. ADR 0160 D6.2
# considered both and left them: the 21 bench handoffs carry the measurement narrative for the ~133
# data files beside them, so untracking either half strips the rationale from records that stay.
# Re-proposing this is re-deriving a decision that is already written down.

# A PR BODY IS SCRATCH, NOT A DELIVERABLE, AND ONE ALREADY REACHED A BRANCH TIP ONCE.
# `PR_BODY.md` was committed at the repo root on 2026-09-03 because a push hold stopped a pull
# request being opened, so the body had nowhere else to live. That is a real need and the file is
# genuinely useful in the worktree -- but it describes ONE branch, it goes stale the moment that
# branch merges, and `main` is the one place it must never land. Untracked-but-present is exactly
# the state that serves both, so the rule is here rather than in a reviewer's memory.
# Rooted with a leading slash: only the repo root, never a `docs/` or `tests/` file of that name.
/PR_BODY.md

# `mefor-coord/` AT A WORK-TREE ROOT IS DELIBERATELY NOT IGNORED. DO NOT ADD IT HERE.
# The allocator writes claims under <git-common-dir>/mefor-coord/, which is inside .git -- already
# outside the work tree, already invisible to `git status`, already impossible to commit. So a
# `mefor-coord/` directory at a work-tree root is never a legitimate artifact. It means something
# built that path from an empty or relative base and it landed in the process cwd.
# Measured 2026-09-18: a test helper read `git rev-parse --git-common-dir` through a wrapper that
# swallowed git's exit code, got "", and wrote five claim records to a worktree root.
# Ignoring it would buy protection from `git add -A` at the price of deleting the only signal that
# the leak happened at all -- and the signal is what surfaced this defect. The protection is bought
# instead by a guard that fails the test which leaks: tests/conftest.py,
# _no_allocation_records_in_the_checkout. A leak that shows up beats one that hides.
