From 5c0e9190149daacd55da50fc66ae8c0c6e91c334 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Tue, 23 Jun 2026 02:46:50 -0400 Subject: [PATCH 1/2] docs: add AGENTS.md for agent discoverability --- AGENTS.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e970f71 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,30 @@ +# configdrift + +## Purpose +CLI tool that detects and fixes configuration file drift across environments (dev/staging/prod). Supports YAML, JSON, TOML, and .env formats. + +## Build & Test Commands +- Install: `pip install -e .` or `pip install git+https://github.com/Coding-Dev-Tools/configdrift.git` +- Test: `pytest tests/` (or `python -m pytest tests/ -v --tb=short`) +- Lint: `ruff check src/ tests/` +- Build: `pip install build twine && python -m build && twine check dist/*` +- CLI check: `configdrift --help` + +## Architecture +Key directories: +- `src/configdrift/` — Main package (CLI, config parsers, drift detection, compliance) +- `tests/` — Test suite +- `.github/workflows/` — CI/CD (auto-code-review.yml, ci.yml, pages.yml, publish.yml) +- `dist/` — Built distributions + +## Conventions +- Language: Python 3.10+ +- Test framework: pytest (with coverage) +- CI: GitHub Actions (matrix: Python 3.10, 3.11, 3.12, 3.13) +- Linting: ruff (line-length 120, target py310) +- Formatting: ruff +- Package layout: src/ layout with setuptools +- Type checking: py.typed included +- Dependencies: typer, rich, pyyaml, tomli, tomli-w +- CLI entry point: configdrift.cli:app +- Default branch: main \ No newline at end of file From 3ca8f2960df1e65a2fb3f8e44dd70f2fd88a31d1 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Wed, 24 Jun 2026 15:15:51 -0400 Subject: [PATCH 2/2] fix: eliminate B008 noqa hacks with module-level defaults; handle None flattening --- src/configdrift/cli.py | 28 +++++++++++++++++----------- src/configdrift/loader.py | 2 ++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/configdrift/cli.py b/src/configdrift/cli.py index 2b0a565..c6842e7 100644 --- a/src/configdrift/cli.py +++ b/src/configdrift/cli.py @@ -56,19 +56,25 @@ class OutputFormat(str, Enum): SILENT = "silent" +# Module-level defaults to avoid B008 (function calls in argument defaults) +_DEFAULT_BASELINE = "dev" +_DEFAULT_TARGET = "target" +_DEFAULT_OUTPUT: OutputFormat = OutputFormat.TABLE +_DEFAULT_STRICT = False +_FILES_ARG = typer.Argument(..., help="Config files to compare (2+ files; first file is baseline).") +_BASELINE_OPT = typer.Option(_DEFAULT_BASELINE, "--baseline", "-b", help="Baseline environment label (default: 'dev').") +_TARGET_OPT = typer.Option(_DEFAULT_TARGET, "--target", "-t", help="Target environment label (default: 'target').") +_OUTPUT_OPT = typer.Option(_DEFAULT_OUTPUT, "--output", "-o", help="Output format: table, json, or silent (exit code only).") +_STRICT_OPT = typer.Option(_DEFAULT_STRICT, "--strict", help="Exit 1 on ANY drift, not just breaking changes.") + + @app.command() def check( - files: list[str] = typer.Argument(..., help="Config files to compare (2+ files; first file is baseline)."), # noqa: B008 - baseline: str = typer.Option( - "dev", "--baseline", "-b", - help="Baseline environment label (default: 'dev').", - ), # noqa: B008 - target: str = typer.Option("target", "--target", "-t", help="Target environment label (default: 'target')."), # noqa: B008 - output: OutputFormat = typer.Option( - OutputFormat.TABLE, "--output", "-o", - help="Output format: table, json, or silent (exit code only).", - ), # noqa: B008 - strict: bool = typer.Option(False, "--strict", help="Exit 1 on ANY drift, not just breaking changes."), # noqa: B008 + files: list[str] = _FILES_ARG, + baseline: str = _BASELINE_OPT, + target: str = _TARGET_OPT, + output: OutputFormat = _OUTPUT_OPT, + strict: bool = _STRICT_OPT, ): """Compare 2+ config files and report drift. Exits 1 if breaking drift found (useful for CI gating).""" if len(files) < 2: diff --git a/src/configdrift/loader.py b/src/configdrift/loader.py index 1e662a8..db9fe68 100644 --- a/src/configdrift/loader.py +++ b/src/configdrift/loader.py @@ -107,6 +107,8 @@ def _flatten_nested(d: dict[str, Any], prefix: str = "") -> dict[str, Any]: full_key = f"{prefix}.{key}" if prefix else key if isinstance(value, dict): result.update(_flatten_nested(value, full_key)) + elif value is None: + result[full_key] = "" else: result[full_key] = str(value) if not isinstance(value, str | int | float | bool) else value return result