# Vidhi — Configuration Management Library

## Overview
Vidhi is a Python configuration management library built on frozen dataclasses with CLI parsing, YAML loading, polymorphic configs, shortcuts, positional args, and subcommand support. It powers the CLI for projects like smriti and vajra.

## Build System
Always use the Makefile:
```bash
make format    # autoflake + isort + black
make lint      # black --check + isort --check + autoflake --check + pyright + codespell
make test      # pytest tests/ -v
make build     # wheel + sdist
```

## Public API (`vidhi/__init__.py`)
- `frozen_dataclass` — decorator for immutable config classes
- `field(default, help=, name=, positional=, shortcuts=, ...)` — enhanced dataclass field with CLI metadata
- `parse_cli_args(ConfigClass)` — parse `sys.argv` into a config instance (flat or BaseCommand)
- `parse_cli_sweep(ConfigClass)` — parse CLI into multiple configs (grid sweeps)
- `with_cli_overrides(instance)` — override a programmatic config from CLI
- `BasePolyConfig` — base class for polymorphic (variant) configurations
- `BaseCommand` — base class for subcommand-based CLIs (name, alias, default)
- `load_yaml_config(ConfigClass, path)` — load config from YAML
- `create_class_from_dict(ConfigClass, dict)` — instantiate config from dict

## Architecture
```
vidhi/
├── cli.py              # Top-level parse_cli_args / parse_cli_sweep / with_cli_overrides / _parse_subcommand
├── cli_parser.py       # ArgParser — flag parsing, help generation, shortcut resolution, positional handling
├── cli_completion.py   # Shell completion (bash/zsh/fish)
├── base_command.py     # BaseCommand with __init_subclass__ for subcommand registration
├── base_poly_config.py # BasePolyConfig for polymorphic variant configs
├── frozen_dataclass.py # @frozen_dataclass decorator, FrozenDataclassMixin
├── flat_dataclass/     # Nested dataclass flattening (state, explosion, reconstruction, validation, cli)
├── schema.py           # JSON schema export
├── utils.py            # YAML/dict/JSON serialization, is_optional, get_inner_type
├── types.py            # Type definitions
└── constants.py        # Shared constants
```

## Coding Standards
- **Formatter**: black (line-length 100) + isort
- **Linting**: autoflake (unused imports), pyright (type checking), codespell
- **Type hints**: required on all public functions
- **Python target**: 3.12+
- **Imports**: standard lib → third-party → local, sorted by isort

## Key Features

### BaseCommand subcommands (`base_command.py`)
Register subcommands via `__init_subclass__` kwargs on frozen_dataclass classes:
- `name=` — command name (first positional arg on CLI)
- `alias=` — short alias
- `default=True` — used when no command specified
- Registries are per-base-class (each intermediate base gets its own `_subcommands` dict)
- `parse_cli_args(BaseClass)` auto-detects BaseCommand and dispatches via `_parse_subcommand`
- Tests: `tests/test_subcommands.py` (30 tests)

```python
@frozen_dataclass
class MyApp(BaseCommand):
    shared: bool = field(False, help="Shared flag")

@frozen_dataclass
class Run(MyApp, name="run", alias="r", default=True):
    target: str = field(".", positional=True)
```

### Positional args (`field(positional=True)`)
One field per config can accept positional (non-flag) values:
- Stored in field metadata as `cli_positional=True`
- Parsed in `cli_parser.py:ArgParser.parse_args` after all flags consumed
- Works with both flat configs and BaseCommand subcommands
- Tests: `tests/test_cli_api.py::TestPositionalArgs` (6 tests)

### Shortcuts (`field(shortcuts={Enum: "flag"})`)
Map Enum values to single-dash flags for concise CLIs:
- Keys must be Enum members, values are flag strings (without `-`)
- Both single-char (`"r"`) and multi-char (`"ld"`) supported
- `-h` is reserved for `--help`
- Parsed in `cli_parser.py:ArgParser._find_shortcut`
- Tests: `tests/test_cli_api.py::TestShortcuts` (10 tests)

### PEP 604 union types
`utils.py:is_optional` handles both `typing.Optional[X]` and `X | None` (PEP 604 `types.UnionType`).

## Testing Patterns
- Use `patch.object(sys, "argv", [...])` to simulate CLI input
- Test both full names and aliases for subcommands
- Test default command resolution (no subcommand specified)
- Test base class fields are inherited by subcommands
- Subcommand registries are per-base-class; use `TestIsolation` pattern
- Shortcut tests: single/multi-char, with flags, defaults, reserved `-h`, string key rejection
- Positional tests: basic, with shortcuts, with flags, default, too many, unexpected
