Contributions are welcome, whether that is reporting an issue, proposing a fix, suggesting a command or improving the documentation.
Working with an AI coding assistant? Its instructions are in AI.md, which Claude
Code, GitHub Copilot, Codex and Kiro all read (through CLAUDE.md,
.github/copilot-instructions.md, AGENTS.md and .kiro/steering/). Edit AI.md and run
just ai; a test fails when a generated copy lags behind.
- Fork the repository and branch from
main. - Run
just sync(oruv sync) to create the environment. - Make your change, with tests.
- Run
just check. It must pass: ruff lint, ruff format check, and the tests with coverage at or above the floor inpyproject.toml.just ciruns everything else CI checks too: Python 3.11, the dependency audit and the build. - Open a pull request using the template. CI then also scans every commit for secrets (gitleaks), audits the locked dependencies (pip-audit), runs the tests on Python 3.11 to 3.14 and on Windows and macOS, measures coverage, builds the wheel, and builds, runs and scans both container images. All of it must pass to merge.
Security issues are the exception: report those privately, as described in SECURITY.md.
The Libre DevOps Python standards are the general reference. The rules that matter most here:
- Target Python 3.11 or later. Type-hint every public function.
- Keep the layering, which
tests/project/test_package_surface.pyenforces:coreis vendor-neutral: errors, the config file, the brand, the token cache, the HTTP client, the command runner, polling, input parsing, query results and logging. It depends on nothing else in the package. Put a shared helper here rather than copying it into a second module.- Each vendor has a shared layer for what all of its features need: for Microsoft,
microsoft/*.pyandmicrosoft/auth(clouds, credentials, CLI runners, token checks, its config section); for ServiceNow,servicenow/*.py(its config section, credentials, the Table API client, role requirements). - Each feature module (
microsoft/entra,microsoft/xdr, ...) depends oncoreand its vendor's shared layer only, and declares the token scopes its calls need inREQUIREMENTS. - A composite module (
microsoft/devices) may also use the features it combines. clisits on top of everything, and nothing imports it.
- Library code raises
LdoErrorsubclasses and never exits the process. Only theclipackage turns errors into messages and exit codes. - Everything is read-only apart from
az use. A new command that writes to a tenant or an instance needs discussion in an issue first. Secrets are read from the environment, never the config file. - Never log or print an access token.
AccessTokenkeeps the value out of itsrepr; keep it that way. - Library modules log through
logging.getLogger(__name__)with lazy%sformatting, and never configure handlers. - Data goes to stdout, and notes, warnings and errors go to stderr, so output can be piped.
- Keep runtime dependencies to
requestsandtyper. Anything else needs a good reason. - Everything must be testable, and tests must not touch the network, a real
azor a real clock. Inject the session, subprocess runner, environment, clock and sleep, and use the fakes intests/fakes:fakes.http(a fakerequestsadapter, androutesfor a few endpoints),fakes.azcli(a fake Azure CLI),fakes.clock(a clock for anything that polls),fakes.workbooks(Excel files built by hand) and the rest. Runjust test-311as well asjust check, so nothing newer than Python 3.11 slips in. - Never hard-code the tool's names. The command, environment variable prefix, config
directory, display name and banner come from
core/brand.py, sojust rebrandcan rename everything;tests/project/test_rebrand.pyrebrands a copy and runs its suite to prove it. - Use UK English and plain ASCII in code, comments and docs: no smart quotes, em or en dashes, or ellipsis glyphs.
- Never include real tokens, tenant ids, subscription ids or host names in code, tests or
issues. Use the placeholder GUIDs and
example.comnames the tests already use. If gitleaks flags a public value the code needs, add the narrowest pattern you can to.gitleaks.tomland say why in the pull request; never widen it to a whole file. - Put tests where the code is:
tests/<path>/test_<module>.pyforsrc/libre_devops_helpers/<path>/<module>.py. A new subpackage needs a matching test directory, andtests/project/test_layout.pyfails until it has one. Share helpers through a module intests/fakes, never by importing another test module. - Keep coverage up.
just coverageshows what a change leaves untested; raisefail_underinpyproject.tomlwhen coverage grows, and never lower it to get a change in. - The container images must keep working as an unprivileged user with a read-only
/work.just image,just image-slimandjust image-scanrun what CI runs.
When you add a command, option or exit code, document it on its page in docs/ (and in the
README's command table for a new group), and in CHANGELOG.md, in the same pull request. A
test runs every example in the docs with --help and follows every link, so a renamed
command or option shows up there.
By contributing, you agree that your contributions are licensed under the MIT Licence that covers the project.