bundlewrap/libs/AGENTS.md
CroneKorkN 04558a9189
docs: scaffold agent-friendly entry points (Phase 1)
introduces a balanced set of agent + human docs:

- root AGENTS.md (with CLAUDE.md symlink) — 5-rule quickstart,
  layout map, mental model, use-case keyed example pointers.
- docs/agents/conventions.md — vault/demagify, eval-loader
  constraints, group inheritance, naming, do-not-touch list,
  suspension idioms, working-style notes.
- docs/agents/commands.md — repo-specific deltas to the fork's
  bw runbook (apt-key offline-verify, *.py_ suspended-node
  visibility, vault-echo rule).
- per-area AGENTS.md for bundles/, nodes/, groups/, libs/,
  hooks/, data/, items/, bin/ — mechanism-focused, no enumeration.
- bundles/AGENTS.template.md — per-bundle doc template with
  optional `## Writes into` section for cross-namespace reactors.

bundlewrap-language reference (item types, dep keywords, reactors,
runbook, three-tier safety envelope) is not duplicated here; we
link out to the fork's AGENTS.md instead.

bw test still green. all internal links resolve. Phase 0 invariants
preserved (libs/hooks docstrings, bin/* # purpose: headers).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:44:45 +02:00

68 lines
2.5 KiB
Markdown

# libs/
## What's here
Shared Python helpers reachable from any bundle as
`repo.libs.<modulename>.<symbol>`. One module per file (no packages).
Discovery is by `ls libs/` plus the one-line module docstring at the
top of each file.
```sh
head -1 libs/*.py
```
## Conventions
- **One file per module.** Filename (without `.py`) is the
importable name. `libs/wireguard.py` exposes `repo.libs.wireguard`.
- **One-line module docstring.** Every `libs/*.py` starts with
`"""<name>: <one-line purpose>."""` so `ls + head` is a working index.
Add one when introducing a new lib; the rule is enforced by the
Phase-0 baseline (`grep -L '"""' libs/*.py` should report zero
files).
- **Pure helpers, no I/O at import.** Libs are imported on every `bw`
invocation. Heavy work or filesystem reads at module scope slow the
whole repo. Use functions, not module-level side effects.
- **No magic-string demagification here.** Libs receive already-
resolved values from bundles or nodes. Don't pass `!password_for:`
strings into libs — they won't be resolved.
- **`repo` and `vault` are globals available at runtime.** A few libs
(`wireguard.py`, etc.) reach `repo.vault.<verb>(...)` directly.
That's intentional inside libs, since libs run in the same loader
scope as bundles.
## How to add a helper
1. Either extract from a bundle that's growing complex, or add a new
`libs/<name>.py` file.
2. Top line: `"""<name>: <one-line purpose>."""`.
3. Pure functions only; document side effects in the docstring if any
slip through.
4. Use it from `bundles/<x>/items.py` or `metadata.py` as
`repo.libs.<name>.<symbol>(...)`.
## Pitfalls
- **Lib changes have repo-wide blast radius.** Every bundle that
imports the lib re-evaluates on the next `bw test` / `bw apply`.
Before changing a lib's API, find consumers:
```sh
git grep -l 'repo.libs.<x>'
```
- **`@cache` is your friend** for deterministic key derivations
(`wireguard`, `rsa`, `ssh`); without it, `bw` recomputes per call,
which is slow.
- **Hashable wrappers.** `libs/hashable.py` exists because raw dicts
and sets aren't hashable, so you can't put them inside metadata
sets. Wrap with `repo.libs.hashable.hashable(...)` before nesting.
## See also
- [`bundles/AGENTS.md`](../bundles/AGENTS.md) — when to extract a
helper into `libs/` instead of duplicating across bundles.
- [`docs/agents/conventions.md`](../docs/agents/conventions.md) —
vault, demagify, naming.
- The fork's [`AGENTS.md`](https://github.com/CroneKorkN/bundlewrap/blob/main/AGENTS.md)
`repo.libs` mechanism.