ferroclass 0.12.0

Hierarchical inventory management tool (reclass compatible)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# AGENTS.md

This file contains essential information for agentic coding agents working in this Rust inventory
management project.

## Project Overview

This is a Rust reimplementation of [reclass](https://reclass.pantsfullofunix.net/),
specifically the [salt-formulas/reclass](https://github.com/salt-formulas/reclass) fork.
The goal is **100% compatibility** with the Python reclass implementation. Any
deviation from Python reclass behavior is a bug and must be documented as a known
incompatibility.

The program functionality is described in [README.md](README.md) (concepts, rules, configuration,
and compatibility) and [docs/process.md](docs/process.md) (detailed processing pipeline).

## Licensing

This project is licensed under the **Mozilla Public License 2.0 (MPL-2.0)**.

- **SPDX License Identifier**: `MPL-2.0`
- **Copyright Holder**: Michael Jansen `<ferroclass@michael-jansen.biz>`
- **License text**: `LICENSES/MPL-2.0.txt`

## Documentation Structure

| File                 | Audience      | Content                                           |
|----------------------|---------------|---------------------------------------------------|
| `README.md`          | Users         | Concepts, rules, configuration, compatibility     |
| `docs/process.md`    | Users         | Detailed processing pipeline (deep-dive)          |
| `docs/conventions.md`| Developers    | Error handling, snafu patterns, development rules |
| `CHANGELOG.md`       | Users         | Release history                                   |
| `CONTRIBUTING.md`    | Contributors  | Build, test, licensing, release process           |
| `docs/todo.md`       | Users         | Planned features and roadmap                      |
| `AGENTS.md`          | AI/Developers | Architecture rules, code style, development guide, agent workflow |

## Agent Workflow

This repository uses a multi-agent workflow for development. Agent definitions are
under `.opencode/agents/` and skills are under `.agents/skills/`.

### plan (lead planner)

The lead planner and coordinator. Analyzes requirements, designs changes, and delegates
to specialized agents. Delegates Rust research questions to `@analyze-rust`, Python
reference questions to `@analyze-python`, and implementation to `@build`.
Does NOT write code — only plans and coordinates.

### build (implementer)

The built-in Build agent, customized with project-specific instructions. Implements
changes based on plans from the `plan` agent. Makes precise code changes, verifies with
`cargo build`/`cargo test`/`cargo clippy`, and reports back. Does NOT plan or make
design decisions — it executes plans. Can delegate Python behavior questions to
`@analyze-python`.

### analyze-rust (Rust analyst)

Researches and analyzes the Rust codebase. Answers questions about architecture,
behavior, and code with precise `file_path:line_number` references. Does NOT plan
changes or coordinate implementation.

### analyze-python (reference analyst)

Read-only agent that analyzes the Python reclass codebase. Invoked by the `plan` or
`build` agents when Python behavior needs to be understood.

Python reference code is expected at `references/reclass/` (clone via `make setup-reclass`)
or `~/Projects/python/reclass/` as fallback.

### build-packaging (packaging specialist)

Subagent that analyzes and modifies RPM packaging configuration (spec files, Makefiles,
changelogs, OBS integration). Invoked by the `plan` or `build` agents for packaging tasks.
Has access to the `rust-rpm-packaging` skill for SUSE/RHEL patterns.

## Python Reference

The Python reclass codebase is the reference implementation for ferroclass. To enable
Python reference analysis, clone reclass into the repo:

```shell
make setup-reclass    # Clones https://github.com/salt-formulas/reclass to references/reclass/
```

Once cloned, the `analyze-python` agent can read Python source code and answer questions
about Python reclass behavior. The definitive documentation for reclass internals is
at `references/reclass/reclass/overview.md`.

Key Python modules for quick reference:

- `references/reclass/reclass/core.py` — Core merging and interpolation logic
- `references/reclass/reclass/config.py` — Configuration handling
- `references/reclass/reclass/defaults.py` — Default configuration values
- `references/reclass/reclass/storage/` — Storage backends (directory, yaml, etc.)

## REUSE Compliance

This project follows the [REUSE Specification](https://reuse.software/spec/) version 3.3
to make licensing and copyright information machine-readable.

### How It Works

- Every source file (`.rs`, etc.) has an SPDX header at the top:
  ```rust
  // SPDX-FileCopyrightText: 2025 Michael Jansen <ferroclass@michael-jansen.biz>
  // SPDX-License-Identifier: MPL-2.0
  ```
- Cover non-source files (Cargo.toml, snapshots, etc.) with a wildcard
  rule in `REUSE.toml`.
- The full license text lives in `LICENSES/MPL-2.0.txt`.

### When Adding New Files

All new `.rs` files **must** include the two-line SPDX header as the first lines
of the file. Files without it will cause `reuse lint` to fail.

### Verification

Run `reuse lint` from the project root to verify compliance. The project must
show zero errors and zero warnings.

### Known Incompatibilities

- **YAML 1.1 vs YAML 1.2**: The Python reclass uses PyYAML, which follows YAML 1.1
  (e.g., `yes`/`no`/`on`/`off` are booleans). Our Rust implementation uses
  `yaml-rust2` which follows YAML 1.2, where these are plain strings. This affects
  how boolean-like values are parsed from inventory files.

## Build/Test Commands

### Core Commands

- `cargo build` - Build the project
- `cargo test` - Run all tests
- `cargo test <test_name>` - Run a specific test
- `cargo clean` - Clean build artifacts
- `cargo run --bin reclass -- <args>` - Run the main binary

### Single Test Examples

```bash
cargo test test_repository_init
cargo test empty_document
cargo test test_default_options
```

### Coverage and Quality

- `cargo tarpaulin` - Generate a coverage report
- `cargo tarpaulin --out html` - Generate an HTML coverage report
- `cargo clippy` - Run lints
- `cargo fmt` - Format code
- `make commit` - Run full commit checks (format, test, clippy, check-manpages)

### Development Workflow

- Use `make` for standard operations (build, test, clean, commit)
- The main binary is located at `target/debug/reclass`
- Tests are located in `tests/` directory and inline in modules

## Code Style Guidelines

### Project Structure

- **Library code**: `src/` with modular organization
- **Binary**: `src/bin/reclass/` - main CLI application
- **Tests**: `tests/` for integration tests, inline unit tests in modules
- **Storage layer**: `src/storage/file_system/` for file system operations

### Error Handling

- **Error type**: Use `snafu` for error handling throughout the codebase
- **Pattern**: `#[derive(Debug, Snafu)]` for error enums
- **Context**: Use `.context()` method to add context to errors
- **Visibility**: Use `#[snafu(visibility(pub(in super::module)))]` for module-level visibility
- **Full conventions**: See [docs/conventions.md]docs/conventions.md for funnel principle,
  display rules, source chain discipline, pass-through elimination, and transparent variants

### Import Organization

```rust
// Standard library imports first
use std::path::{Path, PathBuf};
use std::{fs, path};

// External crates next
use clap::Parser;
use snafu::prelude::*;
use yaml_rust2::YamlLoader;

// Internal imports last (use crate:: prefix)
use crate::inventory::elements::{class_parser, node_parser};
use crate::parser::yaml::{Parser, YamlParser};
```

### Naming Conventions

- **Types**: `PascalCase` for structs, enums, traits
- **Functions/Methods**: `snake_case`
- **Constants**: `SCREAMING_SNAKE_CASE`
- **Modules**: `snake_case` for files/directories
- **Error variants**: `PascalCase` with descriptive names

### Module Organization

- **Public API**: Export types with `pub use` for clean interface
- **Private modules**: Use `pub(crate)` for internal-only modules
- **Module declarations**: Group-related functionality in submodules
- **Re-exports**: Use `pub use` to create clean public interfaces

### Testing Patterns

- **Unit tests**: Use `#[cfg(test)]` modules within source files
- **Integration tests**: Place in `tests/` directory
- **Test naming**: Use descriptive test names with `test_` prefix
- **Assertions**: Use standard `assert_eq!`, `assert!`, `expect()` methods

### Code Organization

- **Builder pattern**: Use for complex struct construction (see `ClassBuilder`)
- **Traits**: Define behaviors with `trait` declarations
- **Iterator pattern**: Implement custom iterators for data traversal
- **Option/Result**: Use `Option<T>` and `Result<T, E>` for error handling

### Logging and Debugging

- **Logging**: Use `tracing` crate with the appropriate level macros
- **Debug**: Use `#[derive(Debug)]` for all public types
- **Debug output**: Use `dbg!()` for temporary debugging (remove before commit)

### Configuration and Options

- **CLI parsing**: Use `clap` with derive macros
- **Configuration**: Separate config types in `configuration.rs`
- **Options module**: Group all option types in `inventory/options/`

### Memory and Performance

- **Ownership**: Follow Rust ownership principles carefully
- **Borrowing**: Prefer borrowing to cloning when possible
- **String handling**: Use `String` for owned data, `&str` for borrowed
- **Collections**: Use appropriate collection types (`Vec`, `HashMap`, etc.)

## Key Dependencies

### Core Dependencies

- `clap` - Command line argument parsing
- `snafu` - Error handling
- `serde` + `serde_yaml` - Serialization/deserialization
- `yaml-rust2` - YAML parsing
- `tracing` + `tracing-subscriber` - Logging
- `walkdir` - File system traversal
- `hashlink` - Linked hash maps for ordered data

### Development Tools

- `rustfmt` - Code formatting (included in toolchain)
- `clippy` - Linting (included in toolchain)
- `rust-analyzer` - IDE support (included in toolchain)

## File Naming and Paths

### Source Organization

- Module files: `snake_case.rs` (e.g., `class_parser.rs`)
- Module directories: `snake_case/mod.rs` structure
- Tests: `tests/` directory for integration tests
- Examples: Use `examples/` directory if needed

### Path Handling

- Use `PathBuf` for owned paths
- Use `&Path` for borrowed path references
- Use `path::absolute()` for canonical paths
- Handle path errors gracefully with proper context

## Testing Strategy

### Test Structure

```rust
#[cfg(test)]
mod tests {
    use super::*;
    use crate::other_module;

    #[test]
    fn test_descriptive_name() {
        // Arrange
        let input = setup_test_data();

        // Act
        let result = function_under_test(&input);

        // Assert
        assert_eq!(result.expected, result.actual);
    }
}
```

### Coverage Goals

- Aim for high test coverage on core business logic
- Test error paths and edge cases
- Use property-based testing for complex data transformations
- Include integration tests for major workflows

## Development Guidelines

### When Making Changes

1. Run `cargo test` to ensure existing tests pass
2. Add or update tests for new functionality
3. Run `cargo clippy` to check for lints
4. Run `cargo fmt` to format code
5. Test manually with the CLI binary if applicable

### Code Review Checklist

- Error handling is comprehensive and idiomatic
- Public API is well-documented and stable
- Tests cover the main functionality and edge cases
- Code follows established naming conventions
- Imports are properly organized
- No `dbg!()` statements left in code
- Logging levels are appropriate

### Architecture Rules

1. **Favor library code over binary code.** Shared logic (output formatting,
   serialization, node metadata structs) belongs in `src/`, not copy-pasted
   across binaries. If two `bin/` programs need the same function, extract it
   to the library crate.

2. **No duplicated functions across binaries.** Identical or near-identical
   functions in separate `bin/` directories are forbidden. Factor the common
   code into `src/` and import it.

3. **Single serialization path per format per data type.** JSON output uses
   `Serialize`. YAML output uses `to_yaml_value()`. These are two different
   formats, so having both is acceptable — but each format has exactly one
   path. No external converter function (like `json_value_to_yaml`) that
   manually destructs the type into an intermediate representation.

4. **Configuration defaults are defined once.** Default values for output
   format, pretty-printing, sorting, etc. live in the library's options
   module. Binaries inherit from the library and never redefine defaults
   independently.

5. **Consistent output pipeline across all binaries.** All binaries use the
   same formatting infrastructure for JSON/YAML, pretty/sorted. The
   `reclass` binary is the reference; `reclass-ansible` and `reclass-salt`
   must share the same code path, not re-implement it.

6. **Domain types serialize themselves.** `Value`, `Key`, and other domain
   types own their serialization logic via `Serialize` impls (for JSON) and
   `to_yaml_value()` methods (for YAML). No external converter function
   (like `json_value_to_yaml`) that manually destructs the type.

7. **Separate data construction from formatting.** Build a domain object
   first, then format it for output. Never mix business logic (merging,
   interpolation, inventory queries) with serialization concerns.

8. **Three-layer architecture.** The codebase is structured in three layers:
    - **Core library** (`inventory` crate) — produces plain Rust types
      (`Node`, `Class`, `Value`, `InventoryMap`). No output formatting.
    - **Adapter layer** (`output` module) — transforms core types into
      adapter-specific output types (`AnsibleInventory`, `InventoryOutput`,
      `TopData`), enriching with metadata (`__reclass__`, `timestamp`, etc.).
    - **Format layer** (`output/mod.rs`) — takes any output type and produces
      the final serialized form: JSON via `Serialize`, YAML via
      `to_yaml_value()`.

9. **Binaries are thin CLI wrappers.** All domain logic — inventory
   construction, output types, serialization, formatting — lives in
   `src/`. Binaries (`bin/reclass/`, `bin/reclass-ansible/`,
   `bin/reclass-salt/`) only parse CLI arguments, call into the library,
   and print output. This is necessary because the library will be exposed
   via FFI (PyO3 for Python, Rustler for Elixir) — adapters must be able
   to call the same functions the binaries call, without duplicating
   logic. Adapter-specific output types (e.g., `AnsibleInventory`) belong
   in `src/output/`, not in `bin/`.

### Performance Considerations

- Avoid unnecessary allocations in hot paths
- Use iterators for processing collections
- Profile with appropriate tools if performance issues arise
- Consider memory usage for large inventory datasets

This file should be updated as the project evolves and new patterns emerge.

## Performance Profiling

### Profiling Commands

To profile the inventory command and identify performance bottlenecks:

```bash
# Build release version with debug symbols for profiling
cargo build --release

# Time the command for baseline measurement
time ./target/release/reclass --inventory --output json --inventory-base-uri $PROJECT_DIR/inventories/e2e/

# Profile with perf (requires root or proper permissions)
perf record -F 99 -g --call-graph dwarf -o /tmp/reclass.perf.data \
  -- ./target/release/reclass --inventory --output json --inventory-base-uri $PROJECT_DIR/inventories/e2e/

# View perf report
perf report -i /tmp/reclass.perf.data --stdio --no-children | head -200
```

### Using cargo-flamegraph

```bash
# Generate flamegraph (may require sudo for perf access)
cargo flamegraph --root -o /tmp/flamegraph.svg --bin reclass -- \
  --inventory --output json --inventory-base-uri $PROJECT_DIR/inventories/e2e/
```

### Test Inventories

For performance and stability testing use the built-in test inventories:

- inventories/e2e (small, used by integration tests)
- inventories/example (small, used by integration tests)
- inventories/python_compat (used for Python compatibility tests)