inclean 0.2.1

A C/C++ #include path normalizer that rewrites includes to resolve cleanly against a minimal -I set.
Documentation
# Contributing to inclean

Thanks for your interest in inclean. This is a small project — the
process is correspondingly light.

## Toolchain

- **Rust 1.91+** (2021 edition).
- **`rustfmt`** and **`clippy`** — install via
  `rustup component add rustfmt clippy` if your toolchain doesn't
  already have them.

No other system dependencies are required.

## Build and test

```sh
cargo build                # debug build
cargo check                # fast type-check
cargo test                 # unit + integration tests
cargo clippy --all-targets # lints
cargo fmt                  # format
```

All four of `cargo test`, `cargo clippy --all-targets`, and
`cargo fmt --check` should pass cleanly before a change is submitted.

## Repository layout

See [docs/architecture.md](docs/architecture.md) for the
module-by-module map. In short:

- `src/cli/*.rs` — clap subcommand handlers (thin).
- `src/pipeline/run.rs` — the orchestrator that every subcommand
  calls into.
- `src/config/*`, `src/lex/*`, `src/rule/*`, `src/index/*`,
  `src/validate/*` — the matching pipeline's building blocks.
- `tests/integration.rs` + `tests/fixtures/` — end-to-end tests.
- `schemas/inclean.toml.schema.json` — generated JSON Schema for
  `inclean.toml`, derived from the structs in `src/config/schema.rs`.
  If you touch those structs (rename a field, add/remove a variant,
  change a serde attribute), regenerate the artifact in the same
  commit:
  ```sh
  cargo run -- schema --output schemas/inclean.toml.schema.json
  ```
  CI runs `cargo run -- schema --check schemas/inclean.toml.schema.json` and will fail the PR if the
  committed schema is stale.

## Adding a feature or fixing a bug

1. **Add (or extend) a fixture.** If the change alters observable
   behavior, add a tiny `inclean.toml` + source-file fixture under
   `tests/fixtures/<name>/`. Fixtures should be the smallest possible
   reproduction of the scenario you're testing.
2. **Add an integration test** in `tests/integration.rs` that drives
   the fixture and asserts on the outcome you care about (exit code,
   conflicts, rewritten text).
3. **Run `cargo test` + `cargo clippy --all-targets` + `cargo fmt`**
   clean.
4. **Open a PR** with a brief description of what changed and why.
   Link the fixture/test that demonstrates the new behavior.

## Code conventions

- **Errors.** Use `anyhow::Result` for high-level errors and
  `.with_context(…)` for I/O and parsing boundaries. `thiserror` is
  reserved for future typed errors at internal module boundaries.
- **Error messages.** When a config or rule-set error references a
  specific rule or `inclean.toml` file, the message must include the
  rule name and the source path so the user can locate the problem.
- **CLI is thin.** `src/cli/*.rs` files parse flags and call
  `pipeline::run`. Don't put pipeline logic in CLI handlers.
- **Comments.** Default to none. Add one when the _why_ is non-
  obvious: a hidden constraint, a subtle invariant, a workaround. Do
  not narrate what code already says.
- **No `unsafe`.** There is none today; keep it that way.

## Releasing

Releases are tag-driven. Pushing a `vMAJOR.MINOR.PATCH` tag to GitHub
triggers `.github/workflows/release.yml`, which validates the tag,
runs the test suite, builds binaries for the five supported targets,
and publishes to crates.io, PyPI, and GitHub Releases in one shot.

Cut a release as follows:

0. **Sanity-check the schema artifact.** Normally enforced by CI on
   every PR, but verify locally before tagging:
   ```sh
   cargo run -- schema --check schemas/inclean.toml.schema.json
   ```
   Should exit 0.

   **If this release introduces a breaking change to `inclean.toml`**
   (renamed/removed field, changed semantics, etc.), also bump
   `MIN_SUPPORTED_INCLEAN_TOML_VERSION` in `src/config/discover.rs` to
   `X.Y.Z` and note "breaking: configs must now set `version >= X.Y.Z`"
   prominently in CHANGELOG. inclean is pre-1.0 and does not ship
   migration shims (see [CLAUDE.md]CLAUDE.md).
1. **Update `CHANGELOG.md`.** Promote the `[Unreleased]` section to
   `[X.Y.Z] — YYYY-MM-DD` (today's date) and re-open an empty
   `[Unreleased]` above it. Update the reference links at the bottom.
2. **Bump `Cargo.toml`** — set `version = "X.Y.Z"`. The `check-tag`
   workflow job compares the tag against this field and refuses to
   publish if they disagree.
3. **Commit and merge to `main`.** Both edits land in the same commit
   (e.g. `chore: release v0.1.2`). The release workflow only fires on
   tags that point at a `main` commit.
4. **Tag and push.** From a clean `main`:
   ```sh
   git tag -a v0.1.2 -m "v0.1.2"
   git push origin v0.1.2
   ```
   The tag must be SemVer with a leading `v`. Pre-release suffixes
   (`v0.2.0-rc.1`) are accepted.

If the workflow fails at the `check-tag` step, delete the remote tag
(`git push origin :v0.1.2`) and the local one (`git tag -d v0.1.2`),
fix the mismatch, and re-tag — do **not** force-push over a published
tag.

## Things outside v1 scope

These have been considered and explicitly excluded. Please discuss
in an issue before submitting a PR for any of them:

- A `[defaults]` block or any project-level fallback for
  `allowed_include_dirs` / `original_include_dirs`. The deliberate
  design is "rule tree with explicit `base`".
- Widening the child-subset invariant. Child rules must never match
  more than their parent.
- Formally checking regex containment between layer-4 patterns.
  Runtime AND-combination is the enforcement.
- File-moving, umbrella-header generation, `extern "C"` wrapping,
  or any other source transformation beyond `#include` rewriting.

## Reporting bugs

Open an issue with the smallest possible reproduction:

- the offending `inclean.toml` (or its relevant rules),
- a one-or-two-file source snippet,
- what `inclean check` printed and what you expected.

If the bug is in `cargo test`, please also include the test name and
the output of `cargo test -- --nocapture <test_name>`.