inclean 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. inclean-0.1.0/.github/workflows/release.yml +199 -0
  2. inclean-0.1.0/.github/workflows/test.yml +56 -0
  3. inclean-0.1.0/.gitignore +12 -0
  4. inclean-0.1.0/CHANGELOG.md +58 -0
  5. inclean-0.1.0/CLAUDE.md +97 -0
  6. inclean-0.1.0/CONTRIBUTING.md +93 -0
  7. inclean-0.1.0/Cargo.lock +536 -0
  8. inclean-0.1.0/Cargo.toml +36 -0
  9. inclean-0.1.0/LICENSE +28 -0
  10. inclean-0.1.0/PKG-INFO +116 -0
  11. inclean-0.1.0/README.md +99 -0
  12. inclean-0.1.0/docs/architecture.md +171 -0
  13. inclean-0.1.0/docs/configuration.md +442 -0
  14. inclean-0.1.0/pyproject.toml +26 -0
  15. inclean-0.1.0/src/cli/apply.rs +60 -0
  16. inclean-0.1.0/src/cli/check.rs +201 -0
  17. inclean-0.1.0/src/cli/diff.rs +17 -0
  18. inclean-0.1.0/src/cli/explain.rs +206 -0
  19. inclean-0.1.0/src/cli/init.rs +129 -0
  20. inclean-0.1.0/src/cli/mod.rs +100 -0
  21. inclean-0.1.0/src/config/constants.rs +554 -0
  22. inclean-0.1.0/src/config/discover.rs +397 -0
  23. inclean-0.1.0/src/config/inherit.rs +844 -0
  24. inclean-0.1.0/src/config/mod.rs +4 -0
  25. inclean-0.1.0/src/config/schema.rs +551 -0
  26. inclean-0.1.0/src/index/header_index.rs +173 -0
  27. inclean-0.1.0/src/index/mod.rs +1 -0
  28. inclean-0.1.0/src/lex/include_line.rs +530 -0
  29. inclean-0.1.0/src/lex/mod.rs +1 -0
  30. inclean-0.1.0/src/lib.rs +8 -0
  31. inclean-0.1.0/src/main.rs +5 -0
  32. inclean-0.1.0/src/pipeline/mod.rs +1 -0
  33. inclean-0.1.0/src/pipeline/run.rs +952 -0
  34. inclean-0.1.0/src/rule/action.rs +1299 -0
  35. inclean-0.1.0/src/rule/engine.rs +1121 -0
  36. inclean-0.1.0/src/rule/glob.rs +185 -0
  37. inclean-0.1.0/src/rule/mod.rs +4 -0
  38. inclean-0.1.0/src/rule/tree.rs +301 -0
  39. inclean-0.1.0/src/util.rs +16 -0
  40. inclean-0.1.0/src/validate/allowed.rs +179 -0
  41. inclean-0.1.0/src/validate/mod.rs +1 -0
  42. inclean-0.1.0/tests/fixtures/action-error/inclean.toml +17 -0
  43. inclean-0.1.0/tests/fixtures/action-error/src/main.c +1 -0
  44. inclean-0.1.0/tests/fixtures/angle-allowed/inclean.toml +23 -0
  45. inclean-0.1.0/tests/fixtures/angle-allowed/include/mylib/foo.h +0 -0
  46. inclean-0.1.0/tests/fixtures/angle-allowed/src/main.c +3 -0
  47. inclean-0.1.0/tests/fixtures/auto-file-dir/inclean.toml +19 -0
  48. inclean-0.1.0/tests/fixtures/auto-file-dir/lib/inner/core.c +1 -0
  49. inclean-0.1.0/tests/fixtures/auto-file-dir/lib/inner/helper.h +0 -0
  50. inclean-0.1.0/tests/fixtures/auto-file-dir/lib/outer.c +1 -0
  51. inclean-0.1.0/tests/fixtures/child-wider/inclean.toml +13 -0
  52. inclean-0.1.0/tests/fixtures/child-wider/main.c +1 -0
  53. inclean-0.1.0/tests/fixtures/cross-chain/inclean.toml +14 -0
  54. inclean-0.1.0/tests/fixtures/cross-chain/src/main.c +1 -0
  55. inclean-0.1.0/tests/fixtures/flat-library/inclean.toml +22 -0
  56. inclean-0.1.0/tests/fixtures/flat-library/include/mylib/internal/bar.h +4 -0
  57. inclean-0.1.0/tests/fixtures/flat-library/include/mylib/internal/foo.h +4 -0
  58. inclean-0.1.0/tests/fixtures/flat-library/src/main.c +7 -0
  59. inclean-0.1.0/tests/fixtures/layer5-ambiguity/inclean.toml +13 -0
  60. inclean-0.1.0/tests/fixtures/layer5-ambiguity/src/a/foo.h +0 -0
  61. inclean-0.1.0/tests/fixtures/layer5-ambiguity/src/b/foo.h +0 -0
  62. inclean-0.1.0/tests/fixtures/layer5-ambiguity/src/main.c +1 -0
  63. inclean-0.1.0/tests/fixtures/layer5-disambiguation/inclean.toml +32 -0
  64. inclean-0.1.0/tests/fixtures/layer5-disambiguation/include/mylib/internal/beta.h +0 -0
  65. inclean-0.1.0/tests/fixtures/layer5-disambiguation/include/mylib/public/alpha.h +0 -0
  66. inclean-0.1.0/tests/fixtures/layer5-disambiguation/src/main.c +2 -0
  67. inclean-0.1.0/tests/fixtures/layer5-under/inclean.toml +19 -0
  68. inclean-0.1.0/tests/fixtures/layer5-under/src/internal/foo.h +0 -0
  69. inclean-0.1.0/tests/fixtures/layer5-under/src/main.c +1 -0
  70. inclean-0.1.0/tests/fixtures/multi-module-library/inclean.toml +10 -0
  71. inclean-0.1.0/tests/fixtures/multi-module-library/include/modA/a1.h +0 -0
  72. inclean-0.1.0/tests/fixtures/multi-module-library/include/modA/a2.h +0 -0
  73. inclean-0.1.0/tests/fixtures/multi-module-library/include/modB/b1.h +0 -0
  74. inclean-0.1.0/tests/fixtures/multi-module-library/include/modB/b2.h +0 -0
  75. inclean-0.1.0/tests/fixtures/multi-module-library/src/moduleA/code.c +2 -0
  76. inclean-0.1.0/tests/fixtures/multi-module-library/src/moduleA/inclean.toml +5 -0
  77. inclean-0.1.0/tests/fixtures/multi-module-library/src/moduleB/code.c +2 -0
  78. inclean-0.1.0/tests/fixtures/multi-module-library/src/moduleB/inclean.toml +5 -0
  79. inclean-0.1.0/tests/fixtures/nested-library/inclean.toml +12 -0
  80. inclean-0.1.0/tests/fixtures/nested-library/include/mylib/core/api.h +0 -0
  81. inclean-0.1.0/tests/fixtures/nested-library/include/mylib/core/detail/alpha.h +0 -0
  82. inclean-0.1.0/tests/fixtures/nested-library/include/mylib/core/detail/beta.h +0 -0
  83. inclean-0.1.0/tests/fixtures/nested-library/src/core/impl.c +1 -0
  84. inclean-0.1.0/tests/fixtures/nested-library/src/core/more/deep.c +1 -0
  85. inclean-0.1.0/tests/fixtures/nested-library/src/main.c +2 -0
  86. inclean-0.1.0/tests/fixtures/trailing-comment-policies/inclean.toml +60 -0
  87. inclean-0.1.0/tests/fixtures/trailing-comment-policies/include/mylib/helper/qux.h +2 -0
  88. inclean-0.1.0/tests/fixtures/trailing-comment-policies/include/mylib/internal/foo.h +2 -0
  89. inclean-0.1.0/tests/fixtures/trailing-comment-policies/include/mylib/legacy/old.h +2 -0
  90. inclean-0.1.0/tests/fixtures/trailing-comment-policies/include/mylib/private/bar.h +2 -0
  91. inclean-0.1.0/tests/fixtures/trailing-comment-policies/include/mylib/private/baz.h +2 -0
  92. inclean-0.1.0/tests/fixtures/trailing-comment-policies/src/main.c +9 -0
  93. inclean-0.1.0/tests/fixtures/validation-keep/inclean.toml +13 -0
  94. inclean-0.1.0/tests/fixtures/validation-keep/include/.gitkeep +0 -0
  95. inclean-0.1.0/tests/fixtures/validation-keep/src/main.c +1 -0
  96. inclean-0.1.0/tests/integration/action_error.rs +34 -0
  97. inclean-0.1.0/tests/integration/angle_allowed.rs +28 -0
  98. inclean-0.1.0/tests/integration/auto_file_dir.rs +52 -0
  99. inclean-0.1.0/tests/integration/child_wider.rs +30 -0
  100. inclean-0.1.0/tests/integration/common.rs +37 -0
  101. inclean-0.1.0/tests/integration/cross_chain.rs +24 -0
  102. inclean-0.1.0/tests/integration/flat_library.rs +95 -0
  103. inclean-0.1.0/tests/integration/init_template.rs +30 -0
  104. inclean-0.1.0/tests/integration/layer5_ambiguity.rs +32 -0
  105. inclean-0.1.0/tests/integration/layer5_disambiguation.rs +29 -0
  106. inclean-0.1.0/tests/integration/layer5_under.rs +32 -0
  107. inclean-0.1.0/tests/integration/multi_module.rs +47 -0
  108. inclean-0.1.0/tests/integration/nested_library.rs +63 -0
  109. inclean-0.1.0/tests/integration/trailing_comment.rs +107 -0
  110. inclean-0.1.0/tests/integration/validation_keep.rs +30 -0
  111. inclean-0.1.0/tests/integration.rs +39 -0
@@ -0,0 +1,199 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ tags:
8
+ - v*.*.*
9
+
10
+ permissions:
11
+ contents: write
12
+
13
+ concurrency:
14
+ group: release-${{ github.ref }}
15
+ cancel-in-progress: false
16
+
17
+ env:
18
+ CARGO_TERM_COLOR: always
19
+ BIN_NAME: inclean
20
+
21
+ jobs:
22
+ check-tag:
23
+ name: validate SemVer tag
24
+ if: github.ref_type == 'tag'
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v6
28
+
29
+ - name: validate tag format and Cargo.toml version match
30
+ shell: bash
31
+ env:
32
+ REF_NAME: ${{ github.ref_name }}
33
+ run: |
34
+ set -euo pipefail
35
+ # SemVer 2.0.0 with required leading "v":
36
+ # vMAJOR.MINOR.PATCH[-prerelease][+build]
37
+ semver_re='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$'
38
+ if [[ ! "$REF_NAME" =~ $semver_re ]]; then
39
+ echo "::error::tag '$REF_NAME' is not valid SemVer (expected vMAJOR.MINOR.PATCH[-prerelease][+build])"
40
+ exit 1
41
+ fi
42
+
43
+ tag_version="${REF_NAME#v}"
44
+ cargo_version=$(awk -F'"' '/^version = "/ { print $2; exit }' Cargo.toml)
45
+
46
+ if [[ -z "$cargo_version" ]]; then
47
+ echo "::error::could not extract version from Cargo.toml"
48
+ exit 1
49
+ fi
50
+
51
+ if [[ "$tag_version" != "$cargo_version" ]]; then
52
+ echo "::error::tag version '$tag_version' does not match Cargo.toml version '$cargo_version' — bump Cargo.toml before tagging"
53
+ exit 1
54
+ fi
55
+
56
+ echo "tag '$REF_NAME' matches Cargo.toml version '$cargo_version'"
57
+
58
+ verify:
59
+ needs: check-tag
60
+ name: verify
61
+ uses: ./.github/workflows/test.yml
62
+
63
+ publish-crate:
64
+ name: publish to crates.io
65
+ needs: [verify, check-tag]
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@v6
69
+
70
+ - uses: dtolnay/rust-toolchain@stable
71
+
72
+ - uses: Swatinem/rust-cache@v2
73
+
74
+ - name: cargo publish
75
+ run: cargo publish --token "${{ secrets.CARGO_REGISTRY_TOKEN }}"
76
+
77
+ build:
78
+ name: build ${{ matrix.triple }}
79
+ needs: [verify, check-tag]
80
+ runs-on: ${{ matrix.os }}
81
+ strategy:
82
+ fail-fast: false
83
+ matrix:
84
+ include:
85
+ - os: ubuntu-latest
86
+ target: x86_64
87
+ triple: x86_64-unknown-linux-gnu
88
+ archive: tar.gz
89
+ - os: ubuntu-latest
90
+ target: aarch64
91
+ triple: aarch64-unknown-linux-gnu
92
+ archive: tar.gz
93
+ - os: macos-latest
94
+ target: x86_64
95
+ triple: x86_64-apple-darwin
96
+ archive: tar.gz
97
+ - os: macos-latest
98
+ target: aarch64
99
+ triple: aarch64-apple-darwin
100
+ archive: tar.gz
101
+ - os: windows-latest
102
+ target: x64
103
+ triple: x86_64-pc-windows-msvc
104
+ archive: zip
105
+ steps:
106
+ - uses: actions/checkout@v6
107
+
108
+ - uses: PyO3/maturin-action@v1
109
+ with:
110
+ target: ${{ matrix.target }}
111
+ args: --release --out dist
112
+ manylinux: auto
113
+ sccache: "true"
114
+
115
+ - uses: actions/upload-artifact@v4
116
+ with:
117
+ name: wheels-${{ matrix.triple }}
118
+ path: dist/*.whl
119
+ if-no-files-found: error
120
+
121
+ - name: package binary (tar.gz)
122
+ if: matrix.archive == 'tar.gz'
123
+ shell: bash
124
+ run: |
125
+ set -euo pipefail
126
+ stage="${BIN_NAME}-${{ matrix.triple }}"
127
+ mkdir -p "bins/${stage}"
128
+ cp "target/${{ matrix.triple }}/release/${BIN_NAME}" "bins/${stage}/"
129
+ cp README.md LICENSE "bins/${stage}/"
130
+ tar -czf "bins/${stage}.tar.gz" -C bins "${stage}"
131
+ rm -rf "bins/${stage}"
132
+
133
+ - name: package binary (zip)
134
+ if: matrix.archive == 'zip'
135
+ shell: pwsh
136
+ run: |
137
+ $ErrorActionPreference = 'Stop'
138
+ $stage = "$env:BIN_NAME-${{ matrix.triple }}"
139
+ New-Item -ItemType Directory -Force -Path "bins/$stage" | Out-Null
140
+ Copy-Item "target/${{ matrix.triple }}/release/$env:BIN_NAME.exe" "bins/$stage/"
141
+ Copy-Item README.md, LICENSE "bins/$stage/"
142
+ Compress-Archive -Path "bins/$stage/*" -DestinationPath "bins/$stage.zip"
143
+ Remove-Item -Recurse -Force "bins/$stage"
144
+
145
+ - uses: actions/upload-artifact@v7
146
+ with:
147
+ name: bins-${{ matrix.triple }}
148
+ path: bins/*
149
+ if-no-files-found: error
150
+
151
+ sdist:
152
+ name: sdist
153
+ needs: [verify, check-tag]
154
+ runs-on: ubuntu-latest
155
+ steps:
156
+ - uses: actions/checkout@v6
157
+
158
+ - uses: PyO3/maturin-action@v1
159
+ with:
160
+ command: sdist
161
+ args: --out dist
162
+
163
+ - uses: actions/upload-artifact@v4
164
+ with:
165
+ name: wheels-sdist
166
+ path: dist/*.tar.gz
167
+ if-no-files-found: error
168
+
169
+ publish-pypi:
170
+ name: publish to PyPI
171
+ needs: [build, sdist]
172
+ runs-on: ubuntu-latest
173
+ permissions:
174
+ id-token: write
175
+ steps:
176
+ - uses: actions/download-artifact@v8
177
+ with:
178
+ pattern: wheels-*
179
+ path: dist
180
+ merge-multiple: true
181
+
182
+ - uses: pypa/gh-action-pypi-publish@release/v1
183
+
184
+ gh-release:
185
+ name: create GitHub Release
186
+ needs: [build, publish-crate]
187
+ runs-on: ubuntu-latest
188
+ steps:
189
+ - uses: actions/download-artifact@v8
190
+ with:
191
+ pattern: bins-*
192
+ path: dist
193
+ merge-multiple: true
194
+
195
+ - uses: softprops/action-gh-release@v2
196
+ with:
197
+ files: dist/*
198
+ generate_release_notes: true
199
+ fail_on_unmatched_files: true
@@ -0,0 +1,56 @@
1
+ name: test
2
+
3
+ on:
4
+ workflow_call:
5
+ pull_request:
6
+ paths-ignore: &non-src-code-paths
7
+ - "**/*.md"
8
+ - "LICENSE"
9
+ - ".gitignore"
10
+ push:
11
+ branches: [main]
12
+ paths-ignore: *non-src-code-paths
13
+
14
+ concurrency:
15
+ group: test-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ env:
19
+ CARGO_TERM_COLOR: always
20
+ RUSTFLAGS: -D warnings
21
+
22
+ jobs:
23
+ lint:
24
+ name: fmt + clippy
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v6
28
+
29
+ - uses: dtolnay/rust-toolchain@stable
30
+ with:
31
+ components: rustfmt, clippy
32
+
33
+ - uses: Swatinem/rust-cache@v2
34
+
35
+ - name: cargo fmt --check
36
+ run: cargo fmt --all -- --check
37
+
38
+ - name: cargo clippy
39
+ run: cargo clippy --all-targets --all-features
40
+
41
+ test:
42
+ needs: lint
43
+ name: test (${{ matrix.os }})
44
+ runs-on: ${{ matrix.os }}
45
+ strategy:
46
+ matrix:
47
+ os: [ubuntu-latest, macos-latest, windows-latest]
48
+ steps:
49
+ - uses: actions/checkout@v6
50
+
51
+ - uses: dtolnay/rust-toolchain@stable
52
+
53
+ - uses: Swatinem/rust-cache@v2
54
+
55
+ - name: cargo test
56
+ run: cargo test --all-features
@@ -0,0 +1,12 @@
1
+ ### OS files
2
+ .DS_Store
3
+ Thumbs.db
4
+ *:Zone.Identifier
5
+
6
+ ### Editor files
7
+ .idea/
8
+ .vscode/
9
+ .claude/
10
+
11
+ ### Build artifacts
12
+ /target
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Changed
11
+
12
+ - **Breaking:** `trailing_comment` schema redesigned to mirror include
13
+ action handling. The four `policy` values (`prepend` / `append` /
14
+ `replace` / `fill_if_absent`) and the `text` field are gone; the new
15
+ shape is `{ match, to, form, spacing }` where `match` is a regex
16
+ over the stripped existing comment body, `to` is the new comment
17
+ body template (with new `${comment.N}` / `${comment.text}`
18
+ placeholders), `form` switches between `"line"` / `"block"` /
19
+ `"preserve"`, and `spacing` controls the gutter before the
20
+ delimiter. `to = ""` removes the trailing comment entirely. See
21
+ `docs/configuration.md` for the migration table and the idempotent
22
+ prepend / append idioms in the Rust `regex` dialect.
23
+
24
+ ## [0.1.0] — 2026-05-21
25
+
26
+ Initial release. Feature-complete for v1.
27
+
28
+ ### Added
29
+
30
+ - Hierarchical `inclean.toml` configuration: one root config plus
31
+ optional sub-directory configs.
32
+ - Pure rule tree with single inheritance via `extends`; globally unique
33
+ rule names; AND-combined field merge at match time.
34
+ - Five-layer matching engine: `paths`, `extensions`, `forms`, `match`
35
+ (layer-4 regex on stripped include content), and `match_resolved`
36
+ (layer-5 constraint on the resolved physical file with ambiguity
37
+ detection against `original_include_dirs`).
38
+ - `@std.*` built-in constants for C/C++ extensions and standard
39
+ headers (C89–C23, C++98–C++23), with list-spread and `_or`
40
+ regex-alternation forms.
41
+ - Four action types: `auto` (resolve + relativize against
42
+ `allowed_include_dirs` or the source file's directory), `rewrite`,
43
+ `keep`, `error`. `${...}` placeholder substitution including
44
+ `${resolved.*}` for layer-5 matches.
45
+ - Rule-tree invariant enforcement at source-scan time:
46
+ `child ⊆ parent` and cross-chain disjointness, reported per-include.
47
+ - Post-action `allowed_include_dirs` validation; empty list is the
48
+ explicit opt-out for system-header rules.
49
+ - Five CLI subcommands: `init`, `check` (three-level via
50
+ `-l/--level config|rules|full`), `diff`, `apply`, `explain`.
51
+ - Per-file processing parallelized with `rayon`; deterministic output
52
+ via post-sort.
53
+ - Distinct exit codes: `0` clean, `2` user-defined `action.error`,
54
+ `3` for any of rule-tree conflict, layer-5 ambiguity, action
55
+ evaluation failure, or `allowed_include_dirs` validation failure.
56
+
57
+ [Unreleased]: https://example.com/compare/0.1.0...HEAD
58
+ [0.1.0]: https://example.com/releases/0.1.0
@@ -0,0 +1,97 @@
1
+ # CLAUDE.md — Notes for Claude Code working in this repo
2
+
3
+ ## What this project is
4
+
5
+ inclean is a Rust CLI that rewrites `#include` directives in C/C++ source
6
+ trees so the library can be consumed with a clean, minimal `-I` list. The
7
+ canonical use case: take an old library whose internal source uses
8
+ `#include "bar.h"` (resolving via `-Isrc/internal`) and rewrite every
9
+ such line so the consumer only needs `-Ipath/to/lib/include`.
10
+
11
+ For the module map and pipeline narrative, see
12
+ [docs/architecture.md](docs/architecture.md); for the full `inclean.toml`
13
+ schema (layers, actions, placeholders, `@std.*` constants), see
14
+ [docs/configuration.md](docs/configuration.md). This file keeps only
15
+ Claude-facing guidance.
16
+
17
+ ## Design source of truth
18
+
19
+ The original design plan lives in
20
+ `/home/inaku/.claude/plans/c-c-inclean-iterative-tome.md` (local to the
21
+ maintainer's machine, not committed). Read it before making non-trivial
22
+ changes. Key design choices that drive the code shape:
23
+
24
+ - **Configuration**: TOML, hierarchical (`inclean.toml` at project root,
25
+ optional `inclean.toml` in any sub-directory). The root config **must**
26
+ declare `[project]` with `root` set; sub-configs **must not** declare
27
+ `[project]` at all.
28
+ - **Rule model**: pure rule tree with single inheritance via `extends`. Rule
29
+ `name` is globally unique across all configs in the project. There is
30
+ **no `[defaults]` block** — users write a `base` rule and others extend it.
31
+ - **`[project]` block is minimal**: only `root`. Everything else
32
+ (`allowed_include_dirs`, `original_include_dirs`) lives on rules.
33
+ - **Five-layer matching** (each layer has a default if unspecified):
34
+ 1. `paths` — gitignore-style file globs
35
+ 2. `extensions` — file extension filter (skipped if layer 1 is an exact path)
36
+ 3. `forms` — set of `"quote"` / `"angle"` / `"macro"`; `"macro"` always errors in v1
37
+ 4. `match` — regex on the stripped include content (no quotes/angles)
38
+ 5. `match_resolved` — only runs when the rule sets it. Resolves the
39
+ include via `original_include_dirs` (must be unique — duplicate hits
40
+ surface as `Layer5Ambiguous` per-include, exit 3); then enforces
41
+ optional `under` (path-prefix) and `match` (path regex) constraints.
42
+ When layer 5 runs, the action gets `${resolved.path}` /
43
+ `${resolved.dir}` / `${resolved.basename}` placeholders.
44
+ - **Inheritance semantics**: runtime AND-combination merges fields; the
45
+ rule-tree invariants ("child's match set ⊆ parent's" + "cross-chain
46
+ disjoint") are enforced at source-scan time by
47
+ `tree::check_chain(match_all(...))`. There is no static lint module —
48
+ the source-level check supersedes it (also covers layer-4 regex).
49
+ - **Mode-dependent winner**: under `CheckMode::Full`, the action runs on
50
+ the deepest rule in the matched chain (the leaf), not the first-by-
51
+ declaration. This makes apply behavior independent of rule declaration
52
+ order.
53
+ - **Action default**: `{ type = "auto", relative_to = "allowed", form = "quote" }`.
54
+ - **`@std.*` built-in constants** (e.g. `@std.cpp.extensions`, `@std.cpp17.system_headers`)
55
+ spread in any string-list field via `@name` syntax.
56
+
57
+ ## Module layout & pipeline data flow
58
+
59
+ Moved to [docs/architecture.md](docs/architecture.md). Read that doc
60
+ for the per-module responsibilities, the six-step pipeline walkthrough
61
+ inside `pipeline::run::run`, and the full list of `IncludeOutcome`
62
+ variants and exit-code semantics.
63
+
64
+ ## Dev workflow
65
+
66
+ ```sh
67
+ cargo check # fast type-check
68
+ cargo test # unit + integration tests
69
+ cargo clippy # lints
70
+ cargo fmt # format
71
+ ```
72
+
73
+ Integration fixtures live under `tests/fixtures/` (small fake libraries).
74
+ Add a new fixture for any non-trivial behavior change.
75
+
76
+ ## Conventions
77
+
78
+ - Use `anyhow::Result` for high-level error returns; `thiserror` for typed
79
+ errors at module boundaries.
80
+ - Rule-set / config errors should pinpoint the offending `inclean.toml`
81
+ path and rule name in the message.
82
+ - Keep `cli/*` files thin — they parse flags and call into `pipeline::run`.
83
+ - The `auto` action requires the resolved file to live under one of the
84
+ matched rule's `allowed_include_dirs`; failure is a hard error and aborts
85
+ the file's apply.
86
+
87
+ ## Things to avoid
88
+
89
+ - Don't introduce a `[defaults]` block or any project-level fallback for
90
+ `allowed_include_dirs` / `original_include_dirs`. The deliberate design
91
+ is "rule tree with explicit `base`".
92
+ - Don't widen the rule subset invariant — child rules should never match
93
+ more than the parent.
94
+ - Don't attempt to formally check regex containment for layer 4. Runtime
95
+ AND-combination is the enforcement; static lint covers layers 1/2/3 only.
96
+ - Don't add file-moving, umbrella-header generation, or `extern "C"`
97
+ wrapping. Out of scope for v1.
@@ -0,0 +1,93 @@
1
+ # Contributing to inclean
2
+
3
+ Thanks for your interest in inclean. This is a small project — the
4
+ process is correspondingly light.
5
+
6
+ ## Toolchain
7
+
8
+ - **Rust 1.91+** (2021 edition).
9
+ - **`rustfmt`** and **`clippy`** — install via
10
+ `rustup component add rustfmt clippy` if your toolchain doesn't
11
+ already have them.
12
+
13
+ No other system dependencies are required.
14
+
15
+ ## Build and test
16
+
17
+ ```sh
18
+ cargo build # debug build
19
+ cargo check # fast type-check
20
+ cargo test # unit + integration tests
21
+ cargo clippy --all-targets # lints
22
+ cargo fmt # format
23
+ ```
24
+
25
+ All four of `cargo test`, `cargo clippy --all-targets`, and
26
+ `cargo fmt --check` should pass cleanly before a change is submitted.
27
+
28
+ ## Repository layout
29
+
30
+ See [docs/architecture.md](docs/architecture.md) for the
31
+ module-by-module map. In short:
32
+
33
+ - `src/cli/*.rs` — clap subcommand handlers (thin).
34
+ - `src/pipeline/run.rs` — the orchestrator that every subcommand
35
+ calls into.
36
+ - `src/config/*`, `src/lex/*`, `src/rule/*`, `src/index/*`,
37
+ `src/validate/*` — the matching pipeline's building blocks.
38
+ - `tests/integration.rs` + `tests/fixtures/` — end-to-end tests.
39
+
40
+ ## Adding a feature or fixing a bug
41
+
42
+ 1. **Add (or extend) a fixture.** If the change alters observable
43
+ behavior, add a tiny `inclean.toml` + source-file fixture under
44
+ `tests/fixtures/<name>/`. Fixtures should be the smallest possible
45
+ reproduction of the scenario you're testing.
46
+ 2. **Add an integration test** in `tests/integration.rs` that drives
47
+ the fixture and asserts on the outcome you care about (exit code,
48
+ conflicts, rewritten text).
49
+ 3. **Run `cargo test` + `cargo clippy --all-targets` + `cargo fmt`**
50
+ clean.
51
+ 4. **Open a PR** with a brief description of what changed and why.
52
+ Link the fixture/test that demonstrates the new behavior.
53
+
54
+ ## Code conventions
55
+
56
+ - **Errors.** Use `anyhow::Result` for high-level errors and
57
+ `.with_context(…)` for I/O and parsing boundaries. `thiserror` is
58
+ reserved for future typed errors at internal module boundaries.
59
+ - **Error messages.** When a config or rule-set error references a
60
+ specific rule or `inclean.toml` file, the message must include the
61
+ rule name and the source path so the user can locate the problem.
62
+ - **CLI is thin.** `src/cli/*.rs` files parse flags and call
63
+ `pipeline::run`. Don't put pipeline logic in CLI handlers.
64
+ - **Comments.** Default to none. Add one when the _why_ is non-
65
+ obvious: a hidden constraint, a subtle invariant, a workaround. Do
66
+ not narrate what code already says.
67
+ - **No `unsafe`.** There is none today; keep it that way.
68
+
69
+ ## Things outside v1 scope
70
+
71
+ These have been considered and explicitly excluded. Please discuss
72
+ in an issue before submitting a PR for any of them:
73
+
74
+ - A `[defaults]` block or any project-level fallback for
75
+ `allowed_include_dirs` / `original_include_dirs`. The deliberate
76
+ design is "rule tree with explicit `base`".
77
+ - Widening the child-subset invariant. Child rules must never match
78
+ more than their parent.
79
+ - Formally checking regex containment between layer-4 patterns.
80
+ Runtime AND-combination is the enforcement.
81
+ - File-moving, umbrella-header generation, `extern "C"` wrapping,
82
+ or any other source transformation beyond `#include` rewriting.
83
+
84
+ ## Reporting bugs
85
+
86
+ Open an issue with the smallest possible reproduction:
87
+
88
+ - the offending `inclean.toml` (or its relevant rules),
89
+ - a one-or-two-file source snippet,
90
+ - what `inclean check` printed and what you expected.
91
+
92
+ If the bug is in `cargo test`, please also include the test name and
93
+ the output of `cargo test -- --nocapture <test_name>`.