bundlewrap/docs/agents/conventions.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

146 lines
5.9 KiB
Markdown

# Conventions
Repo-specific idioms an agent has to know before editing this BundleWrap
config. For bundlewrap-the-language reference (item types, dep keywords,
metadata-reactor semantics, after-change runbook), see the fork's
[`AGENTS.md`](https://github.com/CroneKorkN/bundlewrap/blob/main/AGENTS.md).
## Secrets
Secrets live in `.secrets.cfg` and are referenced from node files by
"demagify" magic strings. The loader (`nodes.py`) walks node dicts and
resolves any leaf string of the form `!<verb>:<arg>`.
| Magic string | Resolves to |
|---|---|
| `!password_for:<id>` | `repo.vault.password_for(id)` |
| `!decrypt:<ciphertext>` | `repo.vault.decrypt(ciphertext)` |
| `!decrypt_file:<path>` | `repo.vault.decrypt_file(path)` |
| `!32_random_bytes_as_base64_for:<id>` | `repo.vault.random_bytes_as_base64_for(id, length=32)` |
Magic strings are only resolved inside **node files** (everything under
`nodes/`). They are *not* resolved in group files, bundle metadata
defaults, or item attributes — call `repo.vault.<verb>(...)` directly
there.
**Never echo decrypted values.** Don't print, log, or paste them, even
in `bw debug` exploration. If you need a sanity check, hash or
fingerprint instead. This applies to AI agents and humans equally.
## Bundlewrap version
The venv runs **editable** from the maintainer's fork:
```
-e git+https://github.com/CroneKorkN/bundlewrap.git@main#egg=bundlewrap
```
Pinned in `requirements.txt`. The fork's `main` tracks upstream
`bundlewrap/bundlewrap` master; the fork's [`AGENTS.md`](https://github.com/CroneKorkN/bundlewrap/blob/main/AGENTS.md)
is the canonical agent-oriented bundlewrap-language reference.
To pull upstream changes into the venv:
```sh
(cd .venv/src/bundlewrap && git pull)
```
## Eval-loaded node and group files
`nodes/*.py` and `groups/*.py` are loaded by `eval()` in `nodes.py` /
`groups.py` (top of the repo). Each file must be a single Python
expression — typically a dict literal.
Consequences:
- **No top-level imports.** No `from foo import bar`, no `import os`.
Helpers go in `libs/` and are reached via `repo.libs.<x>` from
bundle code, not from node files.
- **No statements.** No `if`/`for`/`def` at the top level. Use
expressions (ternaries, comprehensions) instead.
- **Silent drop on parse error.** Vanilla bundlewrap silently omits a
node/group whose file fails to eval. The maintainer patched
`groups.py` (commit `dc40295`) to print the error instead — but it
still skips the file. Symptom: `bw nodes` lists fewer nodes than you
expect. Cure: re-read the file and check for accidental imports or
syntax errors.
## Group inheritance order
Metadata merges along this chain (from the fork's docs):
```
all → location → os → machine → applications → node
```
`groups/all.py` is the universal base; `groups/{locations,os,machine,applications}/`
hold the per-axis groups; the node's own metadata wins last.
## Naming conventions
| Where | Convention | Example |
|---|---|---|
| `nodes/*.py` | `<location>.<role>.py` | `home.server.py`, `htz.mails.py` |
| `groups/*.py` | one file per group; subdir = purpose | `groups/applications/nextcloud.py`, `groups/os/debian-13.py` |
| `bundles/<name>` | lowercase, hyphen-separated | `backup-server`, `bind-acme`, `routeros-monitoring` |
| Custom items | `items/<type>.py` | `items/download.py` |
Underscores in bundle names appear only in `_old`-suffixed leftovers
(see below); don't introduce new ones.
## Suspension and soft-delete idioms
These conventions look like dead code; they aren't. Don't clean them up.
- **`*.py_` parked node.** A node file whose name ends in `.py_` is
silently excluded from `bw nodes` (the loader only matches `*.py`).
Used to keep decommissioned-but-not-deleted node configs in tree.
Example: `nodes/htz.l4d2.py_`.
- **"for now / disable / dummy / offline" markers.** Commits or comments
containing these phrases mark deliberate suspensions, not bugs. Check
`git log -- <file>` before re-enabling — the maintainer reverses
these manually when the underlying condition resolves.
- **`_old` / `_old2` directories.** Recovery buffers during big
refactors. Don't delete them without asking, even if they look
orphaned.
## Files agents must not modify
| Path | Why |
|---|---|
| `.secrets.cfg*` | vault key material |
| `.venv/` | local Python environment |
| `.cache/` | bw runtime cache |
| `.bw_debug_history` | shell history for `bw debug` |
| `.envrc` | direnv local-environment config |
| `/.cocoindex_code/` | local code index, not in git |
| `README.md` (root) | maintainer's personal TODO scratchpad — not project docs |
Treat `hooks/` and `items/` (custom item types) with extra care: they
affect `bw`'s behavior for the whole repo, not a single bundle.
## Working style
- **Iterative commits are normal.** The maintainer commits in small
checkpoints (`+`, `fix`, `whitespace`, terse one-liners). Don't
rebase WIP branches without asking. As an agent, prefer
complete-feeling commits over mimicking the iterative style.
- **Burst-state awareness.** Before writing into a subsystem, run
`git log --since='1 month ago' bundles/<x>` (or `nodes/<x>.py`).
≥10 recent commits means the subsystem is in flux; read the most
recent diffs first — your assumptions about its metadata shape may
already be stale.
- **Branch names.** PRs go through self-hosted Gitea/Forgejo. Branch
names are lowercase snake_case, descriptive
(`debian-13`, `htz.mails_debian_13_squash`, `l4d2_the_next`).
## Bundlewrap-version migration recipe
When the next bw major lands:
1. Read the upstream migration guide.
2. `git grep` for affected reactor / item patterns.
3. Rewrite each (one commit per pattern is fine — see Working style).
4. Bump `requirements.txt` last.
Pattern from commit `186d503` (bw 4 → 5).