docs: add CLAUDE.md (catalog rules, validation snippet, commit convention)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RQhTgskDx8LLpD47rzLRS
This commit is contained in:
parent
2b915e2040
commit
a08399085f
1 changed files with 105 additions and 0 deletions
105
CLAUDE.md
Normal file
105
CLAUDE.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## What this repo is
|
||||
|
||||
A single-file catalog: `extensions.json` is the manifest LNbits hosts read
|
||||
via `LNBITS_EXTENSIONS_MANIFESTS`. No server code, no build, no tests, no
|
||||
tags. Every commit on `main` is live for every host that pulls the raw URL
|
||||
(`…/aiolabs/lnbits-extensions/raw/branch/main/extensions.json`), including
|
||||
bohm's native lnbits and the regtest compose stack. Hosts pick up changes
|
||||
on lnbits restart, then upgrade via the admin UI. Audit trail is `git log`;
|
||||
rollback is `git revert`.
|
||||
|
||||
This is an **infra repo: commit straight to `main`**, no PR. The release
|
||||
procedure that leads up to a catalog commit (merge PRs in the extension
|
||||
repo, `chore(release)` bump, test on dev lnbits, tag, hash) lives in
|
||||
`~/dev/CLAUDE.md` under "Extension release procedure". This file only
|
||||
covers the catalog side.
|
||||
|
||||
## Manifest shape
|
||||
|
||||
```
|
||||
{ "featured": [<id>, …], "extensions": [<entry>, …] }
|
||||
```
|
||||
|
||||
Required entry fields: `id`, `repo`, `name`, `version`, `short_description`,
|
||||
`archive`, `hash` (sha256 of the archive zip). Optional: `icon`,
|
||||
`min_lnbits_version`, `max_lnbits_version`, `details_link`. Many entries
|
||||
omit `min_lnbits_version` entirely; copy the previous entry for the same
|
||||
`id` and change only `version`, `archive`, `hash`.
|
||||
|
||||
Entries are grouped by `id` in the file. Insert a new version directly after
|
||||
the last existing entry for that `id`, not at the end of the array.
|
||||
|
||||
## Rules that bite
|
||||
|
||||
- **Add, never overwrite.** A new version is a new object alongside the old
|
||||
ones; installs on the previous version must still resolve it. The one
|
||||
historical exception (`lnurlp: replace v1.3.0-aio.1 with v1.3.0-aio.2`)
|
||||
was a same-day fix of a broken tag.
|
||||
- **Keep 4-space indent and `ensure_ascii=False`.** A 2-space re-serialise
|
||||
once turned a one-entry bump into a whole-file diff and needed a follow-up
|
||||
`chore: restore 4-space JSON formatting` commit. Hand-edit the block, or
|
||||
round-trip with the snippet below (verified byte-identical).
|
||||
- **Aio forks point at `git.atitlan.io/aiolabs/<ext>`, never GitHub.** The
|
||||
three `github.com/lnbits/lnurlp` entries (1.1.3, 1.2.0, 1.3.0) predate the
|
||||
fork and stay only for installs that still run them. Don't add new
|
||||
upstream entries for any extension that has an aio fork; production only
|
||||
sees this manifest, so an upstream URL here bypasses our vetting.
|
||||
- **Aio semver must outrank the upstream tag** (`v1.3.0-aio.2` > `v1.3.0`).
|
||||
Dev instances also see LNbits's built-in catalog; if upstream wins the
|
||||
semver comparison, clicking Upgrade overwrites the git checkout under
|
||||
`~/dev/shared/extensions/`.
|
||||
- **`hash` is the integrity gate.** LNbits refuses to install when the
|
||||
downloaded archive's sha256 differs. Always recompute from the tag's
|
||||
archive URL; never copy a hash from a previous entry.
|
||||
|
||||
## Commands
|
||||
|
||||
Compute the hash for a tagged release (Forgejo archive URL form):
|
||||
|
||||
```sh
|
||||
curl -sL https://git.atitlan.io/aiolabs/<ext>/archive/v<version>.zip | sha256sum
|
||||
```
|
||||
|
||||
Validate the manifest before committing (parse, required fields, duplicate
|
||||
`id`+`version`, and that the `archive` URL's tag matches `version`):
|
||||
|
||||
```sh
|
||||
python3 - <<'EOF'
|
||||
import json, sys
|
||||
d = json.load(open("extensions.json"))
|
||||
req = {"id","repo","name","version","short_description","archive","hash"}
|
||||
seen = set(); bad = 0
|
||||
for e in d["extensions"]:
|
||||
key = (e.get("id"), e.get("version"))
|
||||
for m in (req - e.keys(), ["dup"] if key in seen else [], ["archive!=version"] if f"v{e.get('version')}.zip" not in e.get("archive","") else []):
|
||||
if m: print(key, m); bad += 1
|
||||
seen.add(key)
|
||||
sys.exit(bad)
|
||||
EOF
|
||||
```
|
||||
|
||||
Re-serialise without formatting drift (only if editing programmatically):
|
||||
|
||||
```sh
|
||||
python3 -c "
|
||||
import json
|
||||
d = json.load(open('extensions.json'))
|
||||
open('extensions.json','w').write(json.dumps(d, indent=4, ensure_ascii=False) + '\n')"
|
||||
```
|
||||
|
||||
Verify a committed entry matches the live archive:
|
||||
|
||||
```sh
|
||||
git diff -U0 HEAD~1 | grep -oE '"(archive|hash)": "[^"]+"'
|
||||
```
|
||||
|
||||
## Commit message convention
|
||||
|
||||
One entry per commit, subject `<ext>: add v<version> (<one-line what changed>)`,
|
||||
e.g. `events: add v1.6.1-aio.15 (promo codes enforced)`. The parenthetical
|
||||
is the only human-readable changelog the catalog has, so name the user-visible
|
||||
change, not "bump". Formatting-only fixes use `chore:`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue