From d2ba0b8836bfa5f513696fc0960f1e8c27375815 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 9 Sep 2026 09:27:09 -0300 Subject: [PATCH] chore: install ngit repository skill Add repository guidance for supported coding agents. --- .agents/skills/ngit/SKILL.md | 117 +++++++++++++++++ .agents/skills/ngit/reference/accounts.md | 47 +++++++ .agents/skills/ngit/reference/ci.md | 67 ++++++++++ .agents/skills/ngit/reference/containers.md | 103 +++++++++++++++ .agents/skills/ngit/reference/issues.md | 29 +++++ .agents/skills/ngit/reference/nsites.md | 71 +++++++++++ .agents/skills/ngit/reference/prs.md | 96 ++++++++++++++ .agents/skills/ngit/reference/repositories.md | 119 ++++++++++++++++++ .agents/skills/ngit/reference/sync-config.md | 49 ++++++++ .claude/skills/ngit/SKILL.md | 117 +++++++++++++++++ .claude/skills/ngit/reference/accounts.md | 47 +++++++ .claude/skills/ngit/reference/ci.md | 67 ++++++++++ .claude/skills/ngit/reference/containers.md | 103 +++++++++++++++ .claude/skills/ngit/reference/issues.md | 29 +++++ .claude/skills/ngit/reference/nsites.md | 71 +++++++++++ .claude/skills/ngit/reference/prs.md | 96 ++++++++++++++ .claude/skills/ngit/reference/repositories.md | 119 ++++++++++++++++++ .claude/skills/ngit/reference/sync-config.md | 49 ++++++++ AGENTS.md | 2 + CLAUDE.md | 4 +- 20 files changed, 1401 insertions(+), 1 deletion(-) create mode 100644 .agents/skills/ngit/SKILL.md create mode 100644 .agents/skills/ngit/reference/accounts.md create mode 100644 .agents/skills/ngit/reference/ci.md create mode 100644 .agents/skills/ngit/reference/containers.md create mode 100644 .agents/skills/ngit/reference/issues.md create mode 100644 .agents/skills/ngit/reference/nsites.md create mode 100644 .agents/skills/ngit/reference/prs.md create mode 100644 .agents/skills/ngit/reference/repositories.md create mode 100644 .agents/skills/ngit/reference/sync-config.md create mode 100644 .claude/skills/ngit/SKILL.md create mode 100644 .claude/skills/ngit/reference/accounts.md create mode 100644 .claude/skills/ngit/reference/ci.md create mode 100644 .claude/skills/ngit/reference/containers.md create mode 100644 .claude/skills/ngit/reference/issues.md create mode 100644 .claude/skills/ngit/reference/nsites.md create mode 100644 .claude/skills/ngit/reference/prs.md create mode 100644 .claude/skills/ngit/reference/repositories.md create mode 100644 .claude/skills/ngit/reference/sync-config.md diff --git a/.agents/skills/ngit/SKILL.md b/.agents/skills/ngit/SKILL.md new file mode 100644 index 00000000..b55dc347 --- /dev/null +++ b/.agents/skills/ngit/SKILL.md @@ -0,0 +1,117 @@ +--- +name: ngit +description: Commands and workflows for NIP-34 git collaboration over Nostr with the ngit CLI and git-remote-nostr. Use in any repository with a nostr:// remote for generic collaboration requests (open an Issue, create or review a Pull Request (PR), comment, merge, clone) and whenever a task involves nostr:// URLs, ngit commands, Grasp servers, gitworkshop.dev, Nostr CI status and workflows, software releases and Zapstore publication, OCI container images, or nsite static sites published through Blossom. +license: CC-BY-SA-4.0 +metadata: + version: "1.17" +--- + +# ngit — Nostr Plugin for Git + +ngit makes `git clone`, `fetch`, and `push` work with `nostr://` URLs and adds +a CLI for pull requests, issues, membership, CI results, releases, OCI +containers, and nsites. Repository state (which commit each ref points to) is +published as signed Nostr events and is the source of truth; git objects live +on ordinary git servers, so servers are interchangeable. A grasp server bundles +a relay and a git server and creates the repository automatically when an +announcement lists it. Explanation: https://ngit.dev/how-it-works + +## Where to look + +- **This skill documents ngit v3.** Run `ngit --version` first; the commands + here need 3.0.0 or later. If ngit is missing or older, report that and + offer an install or update: `curl -fsSL https://ngit.dev/install.sh | bash` + installs or replaces `ngit` and `git-remote-nostr`, and `ngit update` works + on v3 or later. Other methods: https://ngit.dev/install +- **`ngit --help`** is the authority for the installed version's + flags and defaults. +- **https://ngit.dev** holds the guides. Any page is available as raw + Markdown at `https://ngit.dev/markdown/.md`; the index is + https://ngit.dev/llms.txt. Web UI: https://gitworkshop.dev + +## Rules + +- **Preserve user and repository choices.** Examples here show syntax, not + policy. Carry through the selected signer, target, hosting, CI, and + replication settings; do not add gates, waits, workflow edits, or + configuration changes that the task or repository did not choose. +- **PR branches MUST start with `pr/`** (e.g. `pr/my-feature`). Any other + branch name is a plain push and never creates a PR. +- **Read ngit output with `--json`.** It is a global option and works at any + position. Stdout is exactly one JSON document; progress and diagnostics go + to stderr. `git` commands have no `--json`. Top-level `command_status` is + `ok` for exit 0 and `error` otherwise; it never describes a nested domain + result (`ngit ci status --json` reports `ok` with + `ci.conclusion: "failure"` unless a gate was requested). +- **Add `--offline` after the first network read** in a session, on commands + that support it. `git fetch origin` also refreshes the cache. +- **Identifiers.** `` accepts `nevent1…`, a 64-char hex ID, or a + unique hex prefix with an optional `#` (quote it: `'#deadbeef'`). JSON `id` + and `reply_to` fields are already `nevent1…`; container publication instead + returns a raw-hex `event_id` plus the repository `naddr`. Reference events + inside `--body` text as `nostr:nevent1…` or `nostr:naddr1…`, never as raw + hex. Never construct a NIP-05 address (`user@domain`); use `npub1…` unless + a NIP-05 address was given to you. +- **Multiline text.** `ngit` options such as `--body` and `--description` + accept real newlines: `--body "$(cat note.md)"`. Git push options cannot + carry newlines: write literal `\n` in a short inline `-o description=…`, and + never convert a file into a push option (open the PR with `ngit send` + instead). +- **Signers.** `--signer ` selects a stored identity + for one `ngit` command; `git -c nostr.signer= + push …` does the same for one git command. Neither changes the configured + login. Never export or pass an nsec merely to switch between configured + accounts. +- **CI.** A successful push says nothing about CI. Nostr CI runs workflows + from `.ngit/act/workflows/`; another provider's directory is not evidence. + When CI matters, query the exact commit or PR with + `ngit ci status --json` and read `ci.state` and `ci.conclusion`, + not `command_status`. +- **Target repository.** With several `nostr://` remotes, pass global + `--repo `; a configured remote name, an naddr, and + a nostr:// URL are all accepted. Without it ngit + infers the target from config and branch tracking and fails rather than + guesses. Before a signing command, check the + `target repository: (source: …)` line on stderr. + +## Detecting a nostr repo + +```bash +git remote -v | grep -q 'nostr://' # primary check, no cache needed +ngit repo --json --offline # full metadata when needed +``` + +`ngit repo` always exits 0, and `is_nostr_repo: false` can be a cold-cache +false negative: if a remote shows `nostr://`, run `git fetch origin` and +retry. The output includes the roster (`members`, `lead_source`, `lead_path`, +`pending_actions`, `health`); read `reference/repositories.md` before +changing membership or hosting. + +## nostr:// URLs + +``` +nostr:/// +nostr://// # relay-hint is a bare domain, e.g. relay.ngit.dev +nostr://@/ # NIP-05, only when explicitly provided +nostr:/// # NIP-AD: the full /path is sent URL-encoded to /.well-known/nostr.json?path= +``` + +Standard git commands accept these URLs directly. + +## Task index + +Read the bundled reference before performing that slice of work; the guide +adds tutorials and background. + +| Task | Bundled reference | ngit.dev guide | +| ---- | ----------------- | -------------- | +| Publish, clone, host repositories; maintainers, moderators | `reference/repositories.md` | `/repositories`, `/maintainers` | +| Open, update, stack, review, merge PRs | `reference/prs.md` | `/pull-requests` | +| Issues | `reference/issues.md` | `/issues` | +| CI results, trust, workflows, ngit in CI jobs | `reference/ci.md` | `/ci`, `/ci/workflows/` | +| Accounts, login, signers, secrets | `reference/accounts.md` | `/accounts` | +| Sync, global flags, git config | `reference/sync-config.md` | `/configuration`, `/troubleshooting` | +| Publish nsites (static sites) | `reference/nsites.md` | `/releases/nsites` | +| Publish OCI containers | `reference/containers.md` | `/releases` | +| Software releases | `ngit release --help` | `/releases` | +| Automation contract, machine-readable docs | this file | `/agents/` | diff --git a/.agents/skills/ngit/reference/accounts.md b/.agents/skills/ngit/reference/accounts.md new file mode 100644 index 00000000..de04a6ca --- /dev/null +++ b/.agents/skills/ngit/reference/accounts.md @@ -0,0 +1,47 @@ +# Accounts — identity, login, secrets + +Read when managing accounts, logins, or credential storage. +Guide: https://ngit.dev/accounts (storage modes, pairing a remote signer for +CI, rotation). + +```bash +ngit account whoami --json --offline # every usable signer with npub, aliases, scope, active state; `account list` is an alias +ngit account login # interactive; the secret goes to the OS credential store or ngit's user-only file store +ngit account login alice # make a stored identity the global default (alias, npub, or exact profile name) +ngit account login --local alice # …this repository's default, including for git push +ngit account login --nsec-file /private/key --alias alice +ngit account login --nbunksec-file /private/connection --alias alice # store an established NIP-46 session +ngit account login --bunker-url bunker://... # pair a remote signer +ngit account login --local -i --alias alice # pair a fresh signer and assign or replace this alias +ngit account login --secret-storage file # bypass the OS store; `git-config` stores plaintext and must be explicit +ngit account create --name "Alice" --json +ngit account export-keys --secret # print only the nsec or nbunksec; --json adds the npub +ngit account logout --json # keeps stored keys; add --forget to delete the secret +ngit account forget-keys --json +ngit --signer alice issue create --subject "Bug" --body "Details" --json # one ngit command as alice +git -c nostr.signer=alice push origin pr/topic # one git command as alice +ngit --nsec-file /private/key # one-shot key for CI; --nbunksec-file for a bunker session +``` + +**Storage.** `auto` (OS store, then the file store), `file`, or `git-config`, +selected with `--secret-storage`, `NGIT_SECRET_STORAGE`, or +`nostr.secret-storage`. Git config holds the credential entry name, not the +secret. Existing plaintext values keep working. + +**Selection.** `--signer` and `nostr.signer` accept an alias, an npub, or an +exact cached profile name. A profile name must match exactly one account that +holds stored credentials, and only the resolved npub is persisted. Selection +fails closed when the selector is missing, ambiguous, or backed by invalid +credentials. + +**Several NIP-46 connections for one npub.** The first connection stays the +default selected by the bare npub; ngit refuses to replace it with another +unaliased connection. Log in with `--alias` to keep an extra connection and +select it by alias. Older ngit versions ignore the exact-session binding and +select the identity's default connection. + +**nbunksec** is a portable established connection: remote-signer pubkey, +client secret, relays, and optional pairing secret. It holds no npub, so +one-shot use resolves the identity from the signer. The `--nbunksec-file` and +`--nsec-file` forms keep secrets out of process arguments. A fresh pairing +needs interactive approval, so unattended runs use a stored connection. diff --git a/.agents/skills/ngit/reference/ci.md b/.agents/skills/ngit/reference/ci.md new file mode 100644 index 00000000..bae037f6 --- /dev/null +++ b/.agents/skills/ngit/reference/ci.md @@ -0,0 +1,67 @@ +# Nostr CI + +Read when checking whether CI ran, interpreting a result, or writing a +workflow that uses ngit. Guides: https://ngit.dev/ci (coordinators, results, +trust, secrets) and https://ngit.dev/ci/workflows/ (what the coordinator +accepts, refuses, and adds compared with GitHub Actions). + +## Workflows + +Nostr CI (ngit-ci) runs workflows from `.ngit/act/workflows/` with +GitHub Actions syntax in Linux containers. `.github/workflows/` is run only by +GitHub Actions on a mirror; the directories are independent, so a check that +must run in both systems needs a file in each. ngit-ci refuses macOS and +Windows `runs-on` labels and job-level `uses:` (reusable workflows); composite +actions in steps work in both systems. + +Read the workflow at the commit under investigation and confirm that its +triggers and steps cover the check in question: + +```bash +git show :.ngit/act/workflows/.yaml +``` + +Install `ngit` and `git-remote-nostr` inside an ngit-ci or GitHub Actions job +with the step-level composite action, which verifies downloads against a +checksum-pinned manifest: + +```yaml +- uses: danconwaydev/setup-ngit@v3 + with: + version: 3.0.0 # optional exact pin; the default `latest` resolves against the action's manifest, not the network +``` + +Source: +`nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/relay.ngit.dev/setup-ngit` +(GitHub mirror `DanConwayDev/setup-ngit`). + +## Query a result + +```bash +ngit ci status --json # first query refreshes relays; no target means HEAD +ngit ci status --json --offline # later cache-only reads +ngit ci status --require-ci-trust maintainer-directed --json # exit non-zero unless green at this floor +``` + +Query the exact commit that introduced the change. A PR target reports only +its latest revision. Read: + +- `ci.state`: pending, running, or concluded. `ci.conclusion` counts only + once the state is concluded. +- `ci.conclusion`: success, failure, cancellation, or another outcome. + `command_status: "ok"` means only that the query worked. +- `ci.runs[].workflow` and `ci.runs[].jobs`: which workflow and job passed or + failed. +- `ci.runs[].integrity`: the commit is present locally and the workflow hash + matches. +- `coverage` and each run's classification and evidence: how completely and + why the result is trusted. Partial coverage is not success. + +If no run appears, report that no matching Nostr CI event was found. Then +check that the workflow existed at that commit, its trigger matched, a +coordinator serves the repository, and the query refreshed the relays before +concluding that CI did not run. + +Trust floors are `maintainer-directed` and `operationally-associated`. +`ngit pr merge --require-ci-trust ` applies the same gate to a merge +when the caller wants one. diff --git a/.agents/skills/ngit/reference/containers.md b/.agents/skills/ngit/reference/containers.md new file mode 100644 index 00000000..471e2c92 --- /dev/null +++ b/.agents/skills/ngit/reference/containers.md @@ -0,0 +1,103 @@ +# Containers — publish OCI images + +Read before publishing an OCI image, updating a container tag, choosing +Blossom storage, or constructing a gateway pull reference. Protocol +background: https://ngit.dev/protocol/software-publishing + +## Model + +`ngit container publish` (alias `ngit oci publish`) uploads the OCI blobs +reachable from the tagged entries of an OCI image layout to Blossom, then +signs a kind-30624 addressable event mapping tags to manifest digests. The +event is bound to the current kind-30617 git repository, so run it inside that +repository with a signer who is a confirmed maintainer. Gateways are read-only: + +```bash +docker pull ncontainer.io//: +``` + +## Publish + +Export an OCI image layout first, for example +`podman push myimage oci:/tmp/myimage:latest`, then publish every tagged root: + +```bash +ngit container publish myimage \ + --layout /tmp/myimage \ + --blossom-server https://blossom-one.example \ + --blossom-server https://blossom-two.example \ + --relay wss://relay.example \ + --source https://example.com/myimage \ + --json +``` + +A checked-in `.ngit/containers.yaml` lets `ngit container publish myimage +--json` select an entry: + +```yaml +schema: 1 +publication: + blossom_servers: [https://blossom-one.example, https://blossom-two.example] + relays: [wss://relay.example] +containers: + myimage: + layout: artifacts/myimage + source: https://example.com/myimage +``` + +Relative paths resolve from the repository root. `--manifest PATH` selects +another file; `--no-manifest` ignores the default and requires `--layout`. A +loaded manifest must define `NAME`. CLI layout and metadata override the +entry, a non-empty CLI Blossom list replaces the configured list, and CLI +relays extend configured relays. Signer selection, `--replace`, and output +mode stay on the command line. + +Behaviour to know: + +- `NAME` is one lowercase OCI repository-name component. +- Tags come from `org.opencontainers.image.ref.name` annotations in + `index.json`; filenames and git tags are irrelevant. `index.json` itself is + never uploaded; ngit merges the layout's tags into the tag map fetched from + the latest kind-30624 event. +- Without `--blossom-server`, ngit uses the publisher's latest kind-10063 + server list. A single server means no redundancy. Every blob is checked on + every server, missing copies are uploaded with bounded retries and + verified, and the event is signed once each blob has at least one confirmed + copy; incomplete replication is reported per server. +- `--relay` extends the repository's relays; account and default relays are + not added. ngit reads the repository relays before and after uploading and + needs at least one success each time. A total preflight failure or a + concurrent-update refusal is safe to retry: uploaded blobs are + content-addressed. Keep a known state-bearing repository relay reachable + when changing relay sets, because a healthy empty relay cannot reveal an + event stranded elsewhere and a publish could then omit old tags. + +## Merge versus replace + +Ordinary publication updates the tags found in the new layout and retains +older tags, previous server hints, omitted metadata, and unknown event tags. +`--replace` publishes only the new layout's tags and selected servers, drops +omitted description, source, and unknown tags, and sets the title to +`--title` or `NAME`. Use it only when the user explicitly wants complete +replacement. + +## JSON + +A successful result has `command: "container.publish"`, a `warnings` array, +and `result` fields: `repository`, `git_repository`, `npub`, `name`, `naddr`, +`manifest_path` (or `null`), raw-hex `event_id` (unlike collaboration +commands' `nevent` ids), `tags` and `updated_tags`, per-blob SHA-256, size, +and per-server placement, final `blossom_servers`, and per-relay `accepted`. +Success means at least one relay accepted the event; inspect every +`result.relays[].accepted` when full fanout matters. Failures use +`command_status: "error"` with `error.details` holding per-blob and +per-server outcomes and possible orphan blobs. + +## Limits + +ngit accepts OCI and Docker v2 manifests and indexes using SHA-256, uploads +only blobs reachable from tagged roots, and rejects missing, oversized, deeply +nested, or mismatched graphs. It snapshots one blob at a time, so allow +temporary disk roughly equal to the largest layer. It does not build images, +push to registries, run a gateway, chunk layers, pull, list, or delete remote +blobs or tags. Software release assets use the separate `ngit release` model. diff --git a/.agents/skills/ngit/reference/issues.md b/.agents/skills/ngit/reference/issues.md new file mode 100644 index 00000000..7dc86ecb --- /dev/null +++ b/.agents/skills/ngit/reference/issues.md @@ -0,0 +1,29 @@ +# Issues — create, view, comment, close + +Read when working with issues. Guide: https://ngit.dev/issues + +```bash +ngit issue create --subject "Bug title" --body "Details as markdown" --label bug --json +ngit issue list --json # add --status closed or --label bug to filter +ngit issue view --json --comments +ngit issue view --json --history # subject, cover-note, label, and status changes +ngit issue comment --body "Reproduced on v2.1" --json +ngit issue comment --body "Thanks!" --reply-to --json +ngit issue close --reason "wontfix" --json +ngit issue resolved --reason "fixed in abc123" --json +ngit issue reopen --reason "regression in v2.3" --json +ngit issue label --label bug --label enhancement --json +ngit issue set-subject --subject "New title" --json +ngit issue set-cover-note --body "$(cat cover-note.md)" --json +``` + +`resolved` records that the problem was fixed; `close` records that it will +not be. Reference other events in `--body` as `nostr:nevent1…`. + +## Auto-resolve from commits + +A commit pushed to the declared default branch resolves an issue when its +message contains a form of `close`, `fix`, `resolve`, or `implement` followed +by a unique hex ID or prefix or a `nostr:nevent1…` reference, for example +`Fixes #deadbeef`. The status is published only when the pusher is the issue +author or a confirmed repository member. diff --git a/.agents/skills/ngit/reference/nsites.md b/.agents/skills/ngit/reference/nsites.md new file mode 100644 index 00000000..04972b7f --- /dev/null +++ b/.agents/skills/ngit/reference/nsites.md @@ -0,0 +1,71 @@ +# Nsites — publish static sites + +Read before publishing an already-built website with `ngit nsite` or +diagnosing its Blossom uploads and NIP-5A manifest. +Guide: https://ngit.dev/releases/nsites (nsyte comparison, PR previews). + +## Publish + +Pass the build output directory, not the source tree. ngit uploads every +regular file, runs no build, applies no ignore files, and rejects symlinks, +unsafe paths, and filenames without extensions. + +```bash +ngit nsite publish dist --json # reads nsyte's .nsite/config.json when present +ngit nsite publish dist --title "My site" --json # root kind-15128 site +ngit nsite publish dist --id docs \ + --description-file site-description.txt \ + --source "nostr:///" \ + --blossom-server https://blossom.example.com \ + --blossom-server https://mirror.example.com \ + --relay wss://relay.example.com --json # named kind-35128 site +ngit --signer nsite publish dist --json +ngit --nbunksec-file /run/secrets/publisher-nbunksec nsite publish dist --json # unattended NIP-46 +``` + +- Config: `.nsite/config.json` (JSON, not YAML) fields `id`, `title`, + `description`, `source`, `fallback`, `servers`, and `relays` are read; + `--config PATH` selects another file and `--no-config` ignores it. Explicit + CLI values win, and any repeated `--blossom-server` or `--relay` replaces + that whole config array. Unsupported nsyte publication options + (`publishProfile`, `publishRelayList`, `publishServerList`, + `publishAppHandler`) produce a warning; nsyte signer fields are ignored. +- Metadata: `--title`, `--description` or `--description-file`, and + `--source` (`https://` or `nostr://`; omitted, ngit infers the selected + public repository and never a private one). NIP-5A has no logo tag; ship a + `favicon.ico` or `favicon.svg` in the build output. +- `--fallback SITE_PATH` (or config `fallback`) maps an existing HTML file in + the output to `/404.html` without another upload. +- Servers: omit `--blossom-server` to use the account's latest kind-10063 + list; repeat it for replication. `--concurrency` (default 4, range 1–64) is + a global limit across presence checks and uploads. + +## Guarantees + +ngit snapshots the directory before network work, deduplicates content, checks +every blob on every selected server, and uploads missing copies with BUD-11 +authorization. It signs the manifest only after every blob has at least one +confirmed copy, so a failed deployment cannot point the live manifest at +missing content. A server that fails three consecutive initial checks is +skipped for the rest of that pass while the others continue. + +Rerun the same command after a failure: blobs already on a server are +confirmed with `HEAD` and skipped, so continuation is per blob and server. An +unchanged deployment reuses the current manifest without a new signature or +relay write. + +## JSON + +Check `command_status`, then: + +- `result.changed`: publication versus an unchanged no-op; +- `result.config_path`, `result.fallback`, `result.relays`: resolved settings; +- `result.blossom.blobs[].servers[]`: each blob and server outcome; +- `result.publication.relays[]`: manifest acknowledgements (at least one + relay must accept); +- `warnings[]`: unknown MIME types, unsupported config publications, and + failed uploads or post-upload verification per server. + +On a Blossom failure inspect `error.details.blobs` and +`error.details.possible_orphan_blobs`, fix the server or signer problem, and +rerun. diff --git a/.agents/skills/ngit/reference/prs.md b/.agents/skills/ngit/reference/prs.md new file mode 100644 index 00000000..2a00fcfa --- /dev/null +++ b/.agents/skills/ngit/reference/prs.md @@ -0,0 +1,96 @@ +# Pull requests — open, update, stack, review, merge + +Read before opening, updating, reviewing, or merging PRs. +Guide: https://ngit.dev/pull-requests + +## Open or update a PR + +The branch name MUST start with `pr/`. No push option turns another branch +into a PR. + +```bash +git checkout -b pr/my-feature +git push -u origin pr/my-feature # one commit: its subject and body become title and description +git push -u origin pr/my-feature \ + -o 'title=My feature' \ + -o 'description=First paragraph.\n\nSecond paragraph.' # literal \n; push options cannot carry real newlines +git push -u origin pr/release-fix -o target-branch=release/2.x +git push -u origin pr/second-part -o base= # pin or override the stack parent +git push --force origin pr/my-feature # update the PR after amending or rebasing +``` + +- `-d`/`--defaults` accepts the single-commit title and description without a + prompt. +- Do not use `$'…\n…'` for push options, and do not pre-escape a Markdown + file into `-o description=`; open the PR with `ngit send` instead. +- Stacks are inferred: a branch that contains the unique latest tip of one of + your other open or draft PRs becomes that PR's child and follows the parent + as it advances. Rebase the child onto the parent's latest tip before updating + it; ngit refuses stale children and ambiguous candidates rather than + guessing. Use `base=` for a cross-author, historical, or ambiguous parent, + and repeat it on each update if the child should stay pinned. +- To push as another stored identity, use + `git -c nostr.signer= push …`; `--signer` applies + to `ngit` commands only. `ngit account login --local ` makes an + identity the repository default instead. + +## ngit send + +`ngit send` takes ordinary shell arguments, so `--description` accepts real +newlines from `$'…'` or `"$(cat file.md)"`. Inside double quotes `\n` stays a +literal backslash-n. + +```bash +ngit send HEAD~2 --subject "My feature" --description "$(cat pr-description.md)" --json +ngit send HEAD~2 --in-reply-to --json # new revision of an existing PR +ngit send --defaults --target-branch release/2.x --json +ngit send --defaults --base --json +``` + +Do not also push a `pr/` branch for the same proposal. + +## Read, comment, check out + +```bash +ngit pr list --json # default filter: open,draft +ngit pr list --json --status open,draft,closed,applied --label bug +ngit pr view --json --comments +ngit pr comment --body "Looks good" --json +ngit pr comment --body "Fixed!" --reply-to --json +ngit pr checkout --json # local tracking branch that git pull/push understand +``` + +## Merge (maintainer) + +```bash +ngit pr merge --json # no-ff merge commit on the PR's target; does not push +ngit pr merge --json # PR inferred from the checked-out pr/ branch +ngit pr merge --require-ci-trust maintainer-directed --json # refuse unless CI is green at this trust floor +ngit pr merge --exclude-description --json # summary line and PR reference only +git push origin # publishes the merge and the applied status +``` + +`ngit merge` is a compatibility alias with the same options. The merge lands +on the PR's declared target, or the default branch, resolved against the +latest Nostr repository state rather than a local tracking ref, with the +message `Merge #<8-hex>: `. Closed and applied PRs are refused +before any git change. On conflicts, resolve them and run `git commit`; the +message is already prepared, and JSON reports `action: "conflicted"` instead +of `"merged"`. + +Before merging or adding maintainer fixes, run +`git log --merges --oneline origin/..HEAD`. A prior `Merge #…` means a +merge would nest merge history; rebase or cherry-pick the PR commits onto the +current target first unless that history is intentional. + +## Lifecycle + +```bash +ngit pr close --reason "blocked by upstream" --json +ngit pr reopen --reason "fix was incomplete" --json +ngit pr ready --reason "addressed review feedback" --json +ngit pr draft --reason "needs more work" --json +ngit pr label --label bug --label enhancement --json +ngit pr set-subject --subject "New title" --json +ngit pr set-cover-note --body "Updated description. See nostr:nevent1abc…" --json +``` diff --git a/.agents/skills/ngit/reference/repositories.md b/.agents/skills/ngit/reference/repositories.md new file mode 100644 index 00000000..7e73060b --- /dev/null +++ b/.agents/skills/ngit/reference/repositories.md @@ -0,0 +1,119 @@ +# Repositories — publish, clone, hosting, membership + +Read when publishing or cloning a repository, resolving `nostr://` URL forms, +or changing an announcement's hosting, metadata, or roster. Guides: +https://ngit.dev/repositories (hosting choices, migrating from a forge, +mirrors, private repositories), https://ngit.dev/maintainers, and +https://ngit.dev/maintainers/going-deeper (leadless repositories, delegated +trust, removal, roster repair). + +## URLs + +``` +nostr:/// +nostr://// # relay-hint is a bare domain, e.g. relay.ngit.dev +nostr://@/ # NIP-05, only when explicitly provided +nostr:/// # NIP-AD: the full /path is sent URL-encoded to /.well-known/nostr.json?path= +``` + +## Clone + +```bash +git clone nostr://// # relay hint skips discovery +git clone nostr:/// +git clone nostr://user@domain.com/ # NIP-05, only if given to you +git clone nostr://ngit.dev/ngit.git # NIP-AD bare-domain path +``` + +Open and draft PRs are not fetched as branches unless `nostr.auto-pr-branches` +is `true`; `ngit pr checkout ` materialises one on demand. + +## Inspect + +```bash +ngit repo --json --offline # run git fetch origin first when the cache may be stale +``` + +The output reports `nostr_url`, effective `git_servers`, `relays`, `hashtags`, +and `grasp_servers` detected from paired clone and relay entries, plus the +roster: `members`, `lead_source`, `lead_path`, `pending_actions`, and +`health`. Follow the actionable error or `pending_actions` rather than +replacing an announcement wholesale. + +## Publish and host + +`ngit init` declares the complete initial announcement. Grasp hosting supplies +both a git server and a relay; additional infrastructure is explicit, empty by +default, and supplements grasp hosting rather than replacing it: + +``` +relays = grasp-derived relays + additional relays +clones = grasp-derived clones + additional clones +``` + +```bash +ngit init --name "My Project" --description "What it does" --defaults --json # preferred grasp servers, else ngit defaults +ngit init --name "My Project" --grasp-server grasp.example.com --defaults --json +ngit init --name "My Project" --additional-relay wss://relay.example.com \ + --additional-clone https://git.example.com/my-project.git --defaults --json +ngit init --name "My Project" --grasp-server "" \ + --additional-relay wss://relay.example.com \ + --additional-clone https://git.example.com/my-project.git --defaults --json # no grasp: both halves required +``` + +Every announcement needs at least one relay and one git server; `init` and +`repo edit` refuse to publish otherwise, including a metadata-only edit of an +announcement that already lacks one half. Repair it in the same command, e.g. +`ngit repo edit --name "New name" --add-grasp-server grasp.example.com`. + +`--identifier` is set at initial publication only: changing the `d` tag +creates a different repository coordinate. + +## Edit + +`ngit repo edit` preserves omitted settings. Collections use targeted, +repeatable actions that can be combined in one command: + +| Setting | Add | Remove | +| ------- | --- | ------ | +| Grasp server | `--add-grasp-server URL` | `--remove-grasp-server URL` | +| Additional relay | `--add-additional-relay URL` | `--remove-additional-relay URL` | +| Additional clone | `--add-additional-clone URL` | `--remove-additional-clone URL` | +| Hashtag | `--add-hashtag TAG` | `--remove-hashtag TAG` | + +Scalars use replacement flags: `--name`, `--description`, `--web`, `--u`, +`--earliest-unique-commit`. A grasp-derived relay or clone cannot be removed +as an additional entry; remove the grasp server and its pair goes with it. To +empty a collection, remove every value currently reported. + +Each successful edit publishes a fresh announcement and, when the repository +has Nostr state, republishes that state once so new relays and servers hold +the authoritative refs. If that fails, follow the reported `ngit sync` +recovery guidance. + +## Roles and membership + +- **Co-maintainer**: publishes git state, merges, manages issues and PRs, and + changes the roster at the protocol level. +- **Lead maintainer**: the same authority plus responsibility for the roster. + When a lead exists, ngit restricts routine roster changes to the lead + workflow. +- **Moderator**: publishes issue, PR, and patch status events (including + recording an existing merge) but cannot publish git state or merge. + +Membership is reciprocal: a listing is an invitation until the invitee +publishes an announcement acknowledging the role, and an invited member's +events are not authoritative until then. + +```bash +ngit repo edit --add-maintainer --json # a sole maintainer's first add makes them lead +ngit repo accept --json # invitee confirms the role and the lead; --grasp-server also hosts the git data there +ngit repo follow-lead --json # members retain history and follow a changed lead or roster +ngit repo leave --json # end your own role and republish +ngit repo edit --remove-maintainer --json +ngit repo edit --lead-maintainer --json # handover: the new lead publishes the full roster first +ngit repo edit --acknowledge-maintainer-change --json +``` + +Change one relationship at a time. In the deliberately leadless case, pass +`--no-lead-maintainer` with every `--add-maintainer` or `--remove-maintainer`. diff --git a/.agents/skills/ngit/reference/sync-config.md b/.agents/skills/ngit/reference/sync-config.md new file mode 100644 index 00000000..8993e879 --- /dev/null +++ b/.agents/skills/ngit/reference/sync-config.md @@ -0,0 +1,49 @@ +# Sync, flags, and configuration + +Read when syncing refs, choosing flags, or tuning git config. +Guides: https://ngit.dev/configuration and https://ngit.dev/troubleshooting + +## Sync + +```bash +ngit sync --json # make git servers reflect the Nostr state for every ref +ngit sync --ref-name main --json # one ref +``` + +## Global flags + +These accept any command position. `--offline` is per command; check +`ngit --help`. + +| Flag | Description | +| ---- | ----------- | +| `--json` | One JSON document on stdout (ngit commands only) | +| `-d`, `--defaults` | Non-interactive; accept defaults | +| `-q`, `--quiet` | Hide non-essential stderr progress (not combinable with `-v`) | +| `--repo ` | Select the target repository | +| `--signer ` | Use a stored signer for this command | +| `--nsec-file`, `--nbunksec-file ` | One-shot key or bunker session from a private file (`--nsec`, `--nbunksec` take inline values) | +| `--repo-relay-only` | Publish only to repository relays | +| `-f`, `--force` | Bypass safety guards | + +## git config + +```bash +ngit --customize # list every option +git config nostr.signer alice # repository signer, including for git push +git config nostr.signer-alias.alice npub1... # portable alias-to-npub mapping +git config nostr.secret-storage file # auto | file | git-config +git config nostr.repo-relay-only true +git config nostr.auto-pr-branches true # fetch every open and draft PR as a pr/* branch (default false) +git config nostr.http-io-timeout-ms 600000 # allow large grasp pushes +NGIT_CACHE_DIR=/writable/path ngit repo --json # override the global event-cache directory +``` + +`nostr.auto-pr-branches` follows normal git config precedence. With the +default `false`, PRs appear as branches only after `ngit pr checkout`; run +`git fetch --prune` once to drop branches fetched by an older version, or use +`git clone --config nostr.auto-pr-branches=true ` to opt in from +the start. + +If the global cache directory is unavailable ngit falls back to an in-memory +cache; the repository's git common directory must still be writable. diff --git a/.claude/skills/ngit/SKILL.md b/.claude/skills/ngit/SKILL.md new file mode 100644 index 00000000..b55dc347 --- /dev/null +++ b/.claude/skills/ngit/SKILL.md @@ -0,0 +1,117 @@ +--- +name: ngit +description: Commands and workflows for NIP-34 git collaboration over Nostr with the ngit CLI and git-remote-nostr. Use in any repository with a nostr:// remote for generic collaboration requests (open an Issue, create or review a Pull Request (PR), comment, merge, clone) and whenever a task involves nostr:// URLs, ngit commands, Grasp servers, gitworkshop.dev, Nostr CI status and workflows, software releases and Zapstore publication, OCI container images, or nsite static sites published through Blossom. +license: CC-BY-SA-4.0 +metadata: + version: "1.17" +--- + +# ngit — Nostr Plugin for Git + +ngit makes `git clone`, `fetch`, and `push` work with `nostr://` URLs and adds +a CLI for pull requests, issues, membership, CI results, releases, OCI +containers, and nsites. Repository state (which commit each ref points to) is +published as signed Nostr events and is the source of truth; git objects live +on ordinary git servers, so servers are interchangeable. A grasp server bundles +a relay and a git server and creates the repository automatically when an +announcement lists it. Explanation: https://ngit.dev/how-it-works + +## Where to look + +- **This skill documents ngit v3.** Run `ngit --version` first; the commands + here need 3.0.0 or later. If ngit is missing or older, report that and + offer an install or update: `curl -fsSL https://ngit.dev/install.sh | bash` + installs or replaces `ngit` and `git-remote-nostr`, and `ngit update` works + on v3 or later. Other methods: https://ngit.dev/install +- **`ngit --help`** is the authority for the installed version's + flags and defaults. +- **https://ngit.dev** holds the guides. Any page is available as raw + Markdown at `https://ngit.dev/markdown/.md`; the index is + https://ngit.dev/llms.txt. Web UI: https://gitworkshop.dev + +## Rules + +- **Preserve user and repository choices.** Examples here show syntax, not + policy. Carry through the selected signer, target, hosting, CI, and + replication settings; do not add gates, waits, workflow edits, or + configuration changes that the task or repository did not choose. +- **PR branches MUST start with `pr/`** (e.g. `pr/my-feature`). Any other + branch name is a plain push and never creates a PR. +- **Read ngit output with `--json`.** It is a global option and works at any + position. Stdout is exactly one JSON document; progress and diagnostics go + to stderr. `git` commands have no `--json`. Top-level `command_status` is + `ok` for exit 0 and `error` otherwise; it never describes a nested domain + result (`ngit ci status --json` reports `ok` with + `ci.conclusion: "failure"` unless a gate was requested). +- **Add `--offline` after the first network read** in a session, on commands + that support it. `git fetch origin` also refreshes the cache. +- **Identifiers.** `` accepts `nevent1…`, a 64-char hex ID, or a + unique hex prefix with an optional `#` (quote it: `'#deadbeef'`). JSON `id` + and `reply_to` fields are already `nevent1…`; container publication instead + returns a raw-hex `event_id` plus the repository `naddr`. Reference events + inside `--body` text as `nostr:nevent1…` or `nostr:naddr1…`, never as raw + hex. Never construct a NIP-05 address (`user@domain`); use `npub1…` unless + a NIP-05 address was given to you. +- **Multiline text.** `ngit` options such as `--body` and `--description` + accept real newlines: `--body "$(cat note.md)"`. Git push options cannot + carry newlines: write literal `\n` in a short inline `-o description=…`, and + never convert a file into a push option (open the PR with `ngit send` + instead). +- **Signers.** `--signer ` selects a stored identity + for one `ngit` command; `git -c nostr.signer= + push …` does the same for one git command. Neither changes the configured + login. Never export or pass an nsec merely to switch between configured + accounts. +- **CI.** A successful push says nothing about CI. Nostr CI runs workflows + from `.ngit/act/workflows/`; another provider's directory is not evidence. + When CI matters, query the exact commit or PR with + `ngit ci status --json` and read `ci.state` and `ci.conclusion`, + not `command_status`. +- **Target repository.** With several `nostr://` remotes, pass global + `--repo `; a configured remote name, an naddr, and + a nostr:// URL are all accepted. Without it ngit + infers the target from config and branch tracking and fails rather than + guesses. Before a signing command, check the + `target repository: (source: …)` line on stderr. + +## Detecting a nostr repo + +```bash +git remote -v | grep -q 'nostr://' # primary check, no cache needed +ngit repo --json --offline # full metadata when needed +``` + +`ngit repo` always exits 0, and `is_nostr_repo: false` can be a cold-cache +false negative: if a remote shows `nostr://`, run `git fetch origin` and +retry. The output includes the roster (`members`, `lead_source`, `lead_path`, +`pending_actions`, `health`); read `reference/repositories.md` before +changing membership or hosting. + +## nostr:// URLs + +``` +nostr:/// +nostr://// # relay-hint is a bare domain, e.g. relay.ngit.dev +nostr://@/ # NIP-05, only when explicitly provided +nostr:/// # NIP-AD: the full /path is sent URL-encoded to /.well-known/nostr.json?path= +``` + +Standard git commands accept these URLs directly. + +## Task index + +Read the bundled reference before performing that slice of work; the guide +adds tutorials and background. + +| Task | Bundled reference | ngit.dev guide | +| ---- | ----------------- | -------------- | +| Publish, clone, host repositories; maintainers, moderators | `reference/repositories.md` | `/repositories`, `/maintainers` | +| Open, update, stack, review, merge PRs | `reference/prs.md` | `/pull-requests` | +| Issues | `reference/issues.md` | `/issues` | +| CI results, trust, workflows, ngit in CI jobs | `reference/ci.md` | `/ci`, `/ci/workflows/` | +| Accounts, login, signers, secrets | `reference/accounts.md` | `/accounts` | +| Sync, global flags, git config | `reference/sync-config.md` | `/configuration`, `/troubleshooting` | +| Publish nsites (static sites) | `reference/nsites.md` | `/releases/nsites` | +| Publish OCI containers | `reference/containers.md` | `/releases` | +| Software releases | `ngit release --help` | `/releases` | +| Automation contract, machine-readable docs | this file | `/agents/` | diff --git a/.claude/skills/ngit/reference/accounts.md b/.claude/skills/ngit/reference/accounts.md new file mode 100644 index 00000000..de04a6ca --- /dev/null +++ b/.claude/skills/ngit/reference/accounts.md @@ -0,0 +1,47 @@ +# Accounts — identity, login, secrets + +Read when managing accounts, logins, or credential storage. +Guide: https://ngit.dev/accounts (storage modes, pairing a remote signer for +CI, rotation). + +```bash +ngit account whoami --json --offline # every usable signer with npub, aliases, scope, active state; `account list` is an alias +ngit account login # interactive; the secret goes to the OS credential store or ngit's user-only file store +ngit account login alice # make a stored identity the global default (alias, npub, or exact profile name) +ngit account login --local alice # …this repository's default, including for git push +ngit account login --nsec-file /private/key --alias alice +ngit account login --nbunksec-file /private/connection --alias alice # store an established NIP-46 session +ngit account login --bunker-url bunker://... # pair a remote signer +ngit account login --local -i --alias alice # pair a fresh signer and assign or replace this alias +ngit account login --secret-storage file # bypass the OS store; `git-config` stores plaintext and must be explicit +ngit account create --name "Alice" --json +ngit account export-keys --secret # print only the nsec or nbunksec; --json adds the npub +ngit account logout --json # keeps stored keys; add --forget to delete the secret +ngit account forget-keys --json +ngit --signer alice issue create --subject "Bug" --body "Details" --json # one ngit command as alice +git -c nostr.signer=alice push origin pr/topic # one git command as alice +ngit --nsec-file /private/key # one-shot key for CI; --nbunksec-file for a bunker session +``` + +**Storage.** `auto` (OS store, then the file store), `file`, or `git-config`, +selected with `--secret-storage`, `NGIT_SECRET_STORAGE`, or +`nostr.secret-storage`. Git config holds the credential entry name, not the +secret. Existing plaintext values keep working. + +**Selection.** `--signer` and `nostr.signer` accept an alias, an npub, or an +exact cached profile name. A profile name must match exactly one account that +holds stored credentials, and only the resolved npub is persisted. Selection +fails closed when the selector is missing, ambiguous, or backed by invalid +credentials. + +**Several NIP-46 connections for one npub.** The first connection stays the +default selected by the bare npub; ngit refuses to replace it with another +unaliased connection. Log in with `--alias` to keep an extra connection and +select it by alias. Older ngit versions ignore the exact-session binding and +select the identity's default connection. + +**nbunksec** is a portable established connection: remote-signer pubkey, +client secret, relays, and optional pairing secret. It holds no npub, so +one-shot use resolves the identity from the signer. The `--nbunksec-file` and +`--nsec-file` forms keep secrets out of process arguments. A fresh pairing +needs interactive approval, so unattended runs use a stored connection. diff --git a/.claude/skills/ngit/reference/ci.md b/.claude/skills/ngit/reference/ci.md new file mode 100644 index 00000000..bae037f6 --- /dev/null +++ b/.claude/skills/ngit/reference/ci.md @@ -0,0 +1,67 @@ +# Nostr CI + +Read when checking whether CI ran, interpreting a result, or writing a +workflow that uses ngit. Guides: https://ngit.dev/ci (coordinators, results, +trust, secrets) and https://ngit.dev/ci/workflows/ (what the coordinator +accepts, refuses, and adds compared with GitHub Actions). + +## Workflows + +Nostr CI (ngit-ci) runs workflows from `.ngit/act/workflows/` with +GitHub Actions syntax in Linux containers. `.github/workflows/` is run only by +GitHub Actions on a mirror; the directories are independent, so a check that +must run in both systems needs a file in each. ngit-ci refuses macOS and +Windows `runs-on` labels and job-level `uses:` (reusable workflows); composite +actions in steps work in both systems. + +Read the workflow at the commit under investigation and confirm that its +triggers and steps cover the check in question: + +```bash +git show :.ngit/act/workflows/.yaml +``` + +Install `ngit` and `git-remote-nostr` inside an ngit-ci or GitHub Actions job +with the step-level composite action, which verifies downloads against a +checksum-pinned manifest: + +```yaml +- uses: danconwaydev/setup-ngit@v3 + with: + version: 3.0.0 # optional exact pin; the default `latest` resolves against the action's manifest, not the network +``` + +Source: +`nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/relay.ngit.dev/setup-ngit` +(GitHub mirror `DanConwayDev/setup-ngit`). + +## Query a result + +```bash +ngit ci status --json # first query refreshes relays; no target means HEAD +ngit ci status --json --offline # later cache-only reads +ngit ci status --require-ci-trust maintainer-directed --json # exit non-zero unless green at this floor +``` + +Query the exact commit that introduced the change. A PR target reports only +its latest revision. Read: + +- `ci.state`: pending, running, or concluded. `ci.conclusion` counts only + once the state is concluded. +- `ci.conclusion`: success, failure, cancellation, or another outcome. + `command_status: "ok"` means only that the query worked. +- `ci.runs[].workflow` and `ci.runs[].jobs`: which workflow and job passed or + failed. +- `ci.runs[].integrity`: the commit is present locally and the workflow hash + matches. +- `coverage` and each run's classification and evidence: how completely and + why the result is trusted. Partial coverage is not success. + +If no run appears, report that no matching Nostr CI event was found. Then +check that the workflow existed at that commit, its trigger matched, a +coordinator serves the repository, and the query refreshed the relays before +concluding that CI did not run. + +Trust floors are `maintainer-directed` and `operationally-associated`. +`ngit pr merge --require-ci-trust ` applies the same gate to a merge +when the caller wants one. diff --git a/.claude/skills/ngit/reference/containers.md b/.claude/skills/ngit/reference/containers.md new file mode 100644 index 00000000..471e2c92 --- /dev/null +++ b/.claude/skills/ngit/reference/containers.md @@ -0,0 +1,103 @@ +# Containers — publish OCI images + +Read before publishing an OCI image, updating a container tag, choosing +Blossom storage, or constructing a gateway pull reference. Protocol +background: https://ngit.dev/protocol/software-publishing + +## Model + +`ngit container publish` (alias `ngit oci publish`) uploads the OCI blobs +reachable from the tagged entries of an OCI image layout to Blossom, then +signs a kind-30624 addressable event mapping tags to manifest digests. The +event is bound to the current kind-30617 git repository, so run it inside that +repository with a signer who is a confirmed maintainer. Gateways are read-only: + +```bash +docker pull ncontainer.io//: +``` + +## Publish + +Export an OCI image layout first, for example +`podman push myimage oci:/tmp/myimage:latest`, then publish every tagged root: + +```bash +ngit container publish myimage \ + --layout /tmp/myimage \ + --blossom-server https://blossom-one.example \ + --blossom-server https://blossom-two.example \ + --relay wss://relay.example \ + --source https://example.com/myimage \ + --json +``` + +A checked-in `.ngit/containers.yaml` lets `ngit container publish myimage +--json` select an entry: + +```yaml +schema: 1 +publication: + blossom_servers: [https://blossom-one.example, https://blossom-two.example] + relays: [wss://relay.example] +containers: + myimage: + layout: artifacts/myimage + source: https://example.com/myimage +``` + +Relative paths resolve from the repository root. `--manifest PATH` selects +another file; `--no-manifest` ignores the default and requires `--layout`. A +loaded manifest must define `NAME`. CLI layout and metadata override the +entry, a non-empty CLI Blossom list replaces the configured list, and CLI +relays extend configured relays. Signer selection, `--replace`, and output +mode stay on the command line. + +Behaviour to know: + +- `NAME` is one lowercase OCI repository-name component. +- Tags come from `org.opencontainers.image.ref.name` annotations in + `index.json`; filenames and git tags are irrelevant. `index.json` itself is + never uploaded; ngit merges the layout's tags into the tag map fetched from + the latest kind-30624 event. +- Without `--blossom-server`, ngit uses the publisher's latest kind-10063 + server list. A single server means no redundancy. Every blob is checked on + every server, missing copies are uploaded with bounded retries and + verified, and the event is signed once each blob has at least one confirmed + copy; incomplete replication is reported per server. +- `--relay` extends the repository's relays; account and default relays are + not added. ngit reads the repository relays before and after uploading and + needs at least one success each time. A total preflight failure or a + concurrent-update refusal is safe to retry: uploaded blobs are + content-addressed. Keep a known state-bearing repository relay reachable + when changing relay sets, because a healthy empty relay cannot reveal an + event stranded elsewhere and a publish could then omit old tags. + +## Merge versus replace + +Ordinary publication updates the tags found in the new layout and retains +older tags, previous server hints, omitted metadata, and unknown event tags. +`--replace` publishes only the new layout's tags and selected servers, drops +omitted description, source, and unknown tags, and sets the title to +`--title` or `NAME`. Use it only when the user explicitly wants complete +replacement. + +## JSON + +A successful result has `command: "container.publish"`, a `warnings` array, +and `result` fields: `repository`, `git_repository`, `npub`, `name`, `naddr`, +`manifest_path` (or `null`), raw-hex `event_id` (unlike collaboration +commands' `nevent` ids), `tags` and `updated_tags`, per-blob SHA-256, size, +and per-server placement, final `blossom_servers`, and per-relay `accepted`. +Success means at least one relay accepted the event; inspect every +`result.relays[].accepted` when full fanout matters. Failures use +`command_status: "error"` with `error.details` holding per-blob and +per-server outcomes and possible orphan blobs. + +## Limits + +ngit accepts OCI and Docker v2 manifests and indexes using SHA-256, uploads +only blobs reachable from tagged roots, and rejects missing, oversized, deeply +nested, or mismatched graphs. It snapshots one blob at a time, so allow +temporary disk roughly equal to the largest layer. It does not build images, +push to registries, run a gateway, chunk layers, pull, list, or delete remote +blobs or tags. Software release assets use the separate `ngit release` model. diff --git a/.claude/skills/ngit/reference/issues.md b/.claude/skills/ngit/reference/issues.md new file mode 100644 index 00000000..7dc86ecb --- /dev/null +++ b/.claude/skills/ngit/reference/issues.md @@ -0,0 +1,29 @@ +# Issues — create, view, comment, close + +Read when working with issues. Guide: https://ngit.dev/issues + +```bash +ngit issue create --subject "Bug title" --body "Details as markdown" --label bug --json +ngit issue list --json # add --status closed or --label bug to filter +ngit issue view --json --comments +ngit issue view --json --history # subject, cover-note, label, and status changes +ngit issue comment --body "Reproduced on v2.1" --json +ngit issue comment --body "Thanks!" --reply-to --json +ngit issue close --reason "wontfix" --json +ngit issue resolved --reason "fixed in abc123" --json +ngit issue reopen --reason "regression in v2.3" --json +ngit issue label --label bug --label enhancement --json +ngit issue set-subject --subject "New title" --json +ngit issue set-cover-note --body "$(cat cover-note.md)" --json +``` + +`resolved` records that the problem was fixed; `close` records that it will +not be. Reference other events in `--body` as `nostr:nevent1…`. + +## Auto-resolve from commits + +A commit pushed to the declared default branch resolves an issue when its +message contains a form of `close`, `fix`, `resolve`, or `implement` followed +by a unique hex ID or prefix or a `nostr:nevent1…` reference, for example +`Fixes #deadbeef`. The status is published only when the pusher is the issue +author or a confirmed repository member. diff --git a/.claude/skills/ngit/reference/nsites.md b/.claude/skills/ngit/reference/nsites.md new file mode 100644 index 00000000..04972b7f --- /dev/null +++ b/.claude/skills/ngit/reference/nsites.md @@ -0,0 +1,71 @@ +# Nsites — publish static sites + +Read before publishing an already-built website with `ngit nsite` or +diagnosing its Blossom uploads and NIP-5A manifest. +Guide: https://ngit.dev/releases/nsites (nsyte comparison, PR previews). + +## Publish + +Pass the build output directory, not the source tree. ngit uploads every +regular file, runs no build, applies no ignore files, and rejects symlinks, +unsafe paths, and filenames without extensions. + +```bash +ngit nsite publish dist --json # reads nsyte's .nsite/config.json when present +ngit nsite publish dist --title "My site" --json # root kind-15128 site +ngit nsite publish dist --id docs \ + --description-file site-description.txt \ + --source "nostr:///" \ + --blossom-server https://blossom.example.com \ + --blossom-server https://mirror.example.com \ + --relay wss://relay.example.com --json # named kind-35128 site +ngit --signer nsite publish dist --json +ngit --nbunksec-file /run/secrets/publisher-nbunksec nsite publish dist --json # unattended NIP-46 +``` + +- Config: `.nsite/config.json` (JSON, not YAML) fields `id`, `title`, + `description`, `source`, `fallback`, `servers`, and `relays` are read; + `--config PATH` selects another file and `--no-config` ignores it. Explicit + CLI values win, and any repeated `--blossom-server` or `--relay` replaces + that whole config array. Unsupported nsyte publication options + (`publishProfile`, `publishRelayList`, `publishServerList`, + `publishAppHandler`) produce a warning; nsyte signer fields are ignored. +- Metadata: `--title`, `--description` or `--description-file`, and + `--source` (`https://` or `nostr://`; omitted, ngit infers the selected + public repository and never a private one). NIP-5A has no logo tag; ship a + `favicon.ico` or `favicon.svg` in the build output. +- `--fallback SITE_PATH` (or config `fallback`) maps an existing HTML file in + the output to `/404.html` without another upload. +- Servers: omit `--blossom-server` to use the account's latest kind-10063 + list; repeat it for replication. `--concurrency` (default 4, range 1–64) is + a global limit across presence checks and uploads. + +## Guarantees + +ngit snapshots the directory before network work, deduplicates content, checks +every blob on every selected server, and uploads missing copies with BUD-11 +authorization. It signs the manifest only after every blob has at least one +confirmed copy, so a failed deployment cannot point the live manifest at +missing content. A server that fails three consecutive initial checks is +skipped for the rest of that pass while the others continue. + +Rerun the same command after a failure: blobs already on a server are +confirmed with `HEAD` and skipped, so continuation is per blob and server. An +unchanged deployment reuses the current manifest without a new signature or +relay write. + +## JSON + +Check `command_status`, then: + +- `result.changed`: publication versus an unchanged no-op; +- `result.config_path`, `result.fallback`, `result.relays`: resolved settings; +- `result.blossom.blobs[].servers[]`: each blob and server outcome; +- `result.publication.relays[]`: manifest acknowledgements (at least one + relay must accept); +- `warnings[]`: unknown MIME types, unsupported config publications, and + failed uploads or post-upload verification per server. + +On a Blossom failure inspect `error.details.blobs` and +`error.details.possible_orphan_blobs`, fix the server or signer problem, and +rerun. diff --git a/.claude/skills/ngit/reference/prs.md b/.claude/skills/ngit/reference/prs.md new file mode 100644 index 00000000..2a00fcfa --- /dev/null +++ b/.claude/skills/ngit/reference/prs.md @@ -0,0 +1,96 @@ +# Pull requests — open, update, stack, review, merge + +Read before opening, updating, reviewing, or merging PRs. +Guide: https://ngit.dev/pull-requests + +## Open or update a PR + +The branch name MUST start with `pr/`. No push option turns another branch +into a PR. + +```bash +git checkout -b pr/my-feature +git push -u origin pr/my-feature # one commit: its subject and body become title and description +git push -u origin pr/my-feature \ + -o 'title=My feature' \ + -o 'description=First paragraph.\n\nSecond paragraph.' # literal \n; push options cannot carry real newlines +git push -u origin pr/release-fix -o target-branch=release/2.x +git push -u origin pr/second-part -o base= # pin or override the stack parent +git push --force origin pr/my-feature # update the PR after amending or rebasing +``` + +- `-d`/`--defaults` accepts the single-commit title and description without a + prompt. +- Do not use `$'…\n…'` for push options, and do not pre-escape a Markdown + file into `-o description=`; open the PR with `ngit send` instead. +- Stacks are inferred: a branch that contains the unique latest tip of one of + your other open or draft PRs becomes that PR's child and follows the parent + as it advances. Rebase the child onto the parent's latest tip before updating + it; ngit refuses stale children and ambiguous candidates rather than + guessing. Use `base=` for a cross-author, historical, or ambiguous parent, + and repeat it on each update if the child should stay pinned. +- To push as another stored identity, use + `git -c nostr.signer= push …`; `--signer` applies + to `ngit` commands only. `ngit account login --local ` makes an + identity the repository default instead. + +## ngit send + +`ngit send` takes ordinary shell arguments, so `--description` accepts real +newlines from `$'…'` or `"$(cat file.md)"`. Inside double quotes `\n` stays a +literal backslash-n. + +```bash +ngit send HEAD~2 --subject "My feature" --description "$(cat pr-description.md)" --json +ngit send HEAD~2 --in-reply-to --json # new revision of an existing PR +ngit send --defaults --target-branch release/2.x --json +ngit send --defaults --base --json +``` + +Do not also push a `pr/` branch for the same proposal. + +## Read, comment, check out + +```bash +ngit pr list --json # default filter: open,draft +ngit pr list --json --status open,draft,closed,applied --label bug +ngit pr view --json --comments +ngit pr comment --body "Looks good" --json +ngit pr comment --body "Fixed!" --reply-to --json +ngit pr checkout --json # local tracking branch that git pull/push understand +``` + +## Merge (maintainer) + +```bash +ngit pr merge --json # no-ff merge commit on the PR's target; does not push +ngit pr merge --json # PR inferred from the checked-out pr/ branch +ngit pr merge --require-ci-trust maintainer-directed --json # refuse unless CI is green at this trust floor +ngit pr merge --exclude-description --json # summary line and PR reference only +git push origin # publishes the merge and the applied status +``` + +`ngit merge` is a compatibility alias with the same options. The merge lands +on the PR's declared target, or the default branch, resolved against the +latest Nostr repository state rather than a local tracking ref, with the +message `Merge #<8-hex>: `. Closed and applied PRs are refused +before any git change. On conflicts, resolve them and run `git commit`; the +message is already prepared, and JSON reports `action: "conflicted"` instead +of `"merged"`. + +Before merging or adding maintainer fixes, run +`git log --merges --oneline origin/..HEAD`. A prior `Merge #…` means a +merge would nest merge history; rebase or cherry-pick the PR commits onto the +current target first unless that history is intentional. + +## Lifecycle + +```bash +ngit pr close --reason "blocked by upstream" --json +ngit pr reopen --reason "fix was incomplete" --json +ngit pr ready --reason "addressed review feedback" --json +ngit pr draft --reason "needs more work" --json +ngit pr label --label bug --label enhancement --json +ngit pr set-subject --subject "New title" --json +ngit pr set-cover-note --body "Updated description. See nostr:nevent1abc…" --json +``` diff --git a/.claude/skills/ngit/reference/repositories.md b/.claude/skills/ngit/reference/repositories.md new file mode 100644 index 00000000..7e73060b --- /dev/null +++ b/.claude/skills/ngit/reference/repositories.md @@ -0,0 +1,119 @@ +# Repositories — publish, clone, hosting, membership + +Read when publishing or cloning a repository, resolving `nostr://` URL forms, +or changing an announcement's hosting, metadata, or roster. Guides: +https://ngit.dev/repositories (hosting choices, migrating from a forge, +mirrors, private repositories), https://ngit.dev/maintainers, and +https://ngit.dev/maintainers/going-deeper (leadless repositories, delegated +trust, removal, roster repair). + +## URLs + +``` +nostr:/// +nostr://// # relay-hint is a bare domain, e.g. relay.ngit.dev +nostr://@/ # NIP-05, only when explicitly provided +nostr:/// # NIP-AD: the full /path is sent URL-encoded to /.well-known/nostr.json?path= +``` + +## Clone + +```bash +git clone nostr://// # relay hint skips discovery +git clone nostr:/// +git clone nostr://user@domain.com/ # NIP-05, only if given to you +git clone nostr://ngit.dev/ngit.git # NIP-AD bare-domain path +``` + +Open and draft PRs are not fetched as branches unless `nostr.auto-pr-branches` +is `true`; `ngit pr checkout ` materialises one on demand. + +## Inspect + +```bash +ngit repo --json --offline # run git fetch origin first when the cache may be stale +``` + +The output reports `nostr_url`, effective `git_servers`, `relays`, `hashtags`, +and `grasp_servers` detected from paired clone and relay entries, plus the +roster: `members`, `lead_source`, `lead_path`, `pending_actions`, and +`health`. Follow the actionable error or `pending_actions` rather than +replacing an announcement wholesale. + +## Publish and host + +`ngit init` declares the complete initial announcement. Grasp hosting supplies +both a git server and a relay; additional infrastructure is explicit, empty by +default, and supplements grasp hosting rather than replacing it: + +``` +relays = grasp-derived relays + additional relays +clones = grasp-derived clones + additional clones +``` + +```bash +ngit init --name "My Project" --description "What it does" --defaults --json # preferred grasp servers, else ngit defaults +ngit init --name "My Project" --grasp-server grasp.example.com --defaults --json +ngit init --name "My Project" --additional-relay wss://relay.example.com \ + --additional-clone https://git.example.com/my-project.git --defaults --json +ngit init --name "My Project" --grasp-server "" \ + --additional-relay wss://relay.example.com \ + --additional-clone https://git.example.com/my-project.git --defaults --json # no grasp: both halves required +``` + +Every announcement needs at least one relay and one git server; `init` and +`repo edit` refuse to publish otherwise, including a metadata-only edit of an +announcement that already lacks one half. Repair it in the same command, e.g. +`ngit repo edit --name "New name" --add-grasp-server grasp.example.com`. + +`--identifier` is set at initial publication only: changing the `d` tag +creates a different repository coordinate. + +## Edit + +`ngit repo edit` preserves omitted settings. Collections use targeted, +repeatable actions that can be combined in one command: + +| Setting | Add | Remove | +| ------- | --- | ------ | +| Grasp server | `--add-grasp-server URL` | `--remove-grasp-server URL` | +| Additional relay | `--add-additional-relay URL` | `--remove-additional-relay URL` | +| Additional clone | `--add-additional-clone URL` | `--remove-additional-clone URL` | +| Hashtag | `--add-hashtag TAG` | `--remove-hashtag TAG` | + +Scalars use replacement flags: `--name`, `--description`, `--web`, `--u`, +`--earliest-unique-commit`. A grasp-derived relay or clone cannot be removed +as an additional entry; remove the grasp server and its pair goes with it. To +empty a collection, remove every value currently reported. + +Each successful edit publishes a fresh announcement and, when the repository +has Nostr state, republishes that state once so new relays and servers hold +the authoritative refs. If that fails, follow the reported `ngit sync` +recovery guidance. + +## Roles and membership + +- **Co-maintainer**: publishes git state, merges, manages issues and PRs, and + changes the roster at the protocol level. +- **Lead maintainer**: the same authority plus responsibility for the roster. + When a lead exists, ngit restricts routine roster changes to the lead + workflow. +- **Moderator**: publishes issue, PR, and patch status events (including + recording an existing merge) but cannot publish git state or merge. + +Membership is reciprocal: a listing is an invitation until the invitee +publishes an announcement acknowledging the role, and an invited member's +events are not authoritative until then. + +```bash +ngit repo edit --add-maintainer --json # a sole maintainer's first add makes them lead +ngit repo accept --json # invitee confirms the role and the lead; --grasp-server also hosts the git data there +ngit repo follow-lead --json # members retain history and follow a changed lead or roster +ngit repo leave --json # end your own role and republish +ngit repo edit --remove-maintainer --json +ngit repo edit --lead-maintainer --json # handover: the new lead publishes the full roster first +ngit repo edit --acknowledge-maintainer-change --json +``` + +Change one relationship at a time. In the deliberately leadless case, pass +`--no-lead-maintainer` with every `--add-maintainer` or `--remove-maintainer`. diff --git a/.claude/skills/ngit/reference/sync-config.md b/.claude/skills/ngit/reference/sync-config.md new file mode 100644 index 00000000..8993e879 --- /dev/null +++ b/.claude/skills/ngit/reference/sync-config.md @@ -0,0 +1,49 @@ +# Sync, flags, and configuration + +Read when syncing refs, choosing flags, or tuning git config. +Guides: https://ngit.dev/configuration and https://ngit.dev/troubleshooting + +## Sync + +```bash +ngit sync --json # make git servers reflect the Nostr state for every ref +ngit sync --ref-name main --json # one ref +``` + +## Global flags + +These accept any command position. `--offline` is per command; check +`ngit --help`. + +| Flag | Description | +| ---- | ----------- | +| `--json` | One JSON document on stdout (ngit commands only) | +| `-d`, `--defaults` | Non-interactive; accept defaults | +| `-q`, `--quiet` | Hide non-essential stderr progress (not combinable with `-v`) | +| `--repo ` | Select the target repository | +| `--signer ` | Use a stored signer for this command | +| `--nsec-file`, `--nbunksec-file ` | One-shot key or bunker session from a private file (`--nsec`, `--nbunksec` take inline values) | +| `--repo-relay-only` | Publish only to repository relays | +| `-f`, `--force` | Bypass safety guards | + +## git config + +```bash +ngit --customize # list every option +git config nostr.signer alice # repository signer, including for git push +git config nostr.signer-alias.alice npub1... # portable alias-to-npub mapping +git config nostr.secret-storage file # auto | file | git-config +git config nostr.repo-relay-only true +git config nostr.auto-pr-branches true # fetch every open and draft PR as a pr/* branch (default false) +git config nostr.http-io-timeout-ms 600000 # allow large grasp pushes +NGIT_CACHE_DIR=/writable/path ngit repo --json # override the global event-cache directory +``` + +`nostr.auto-pr-branches` follows normal git config precedence. With the +default `false`, PRs appear as branches only after `ngit pr checkout`; run +`git fetch --prune` once to drop branches fetched by an older version, or use +`git clone --config nostr.auto-pr-branches=true ` to opt in from +the start. + +If the global cache directory is unavailable ngit falls back to an in-memory +cache; the repository's git common directory must still be writable. diff --git a/AGENTS.md b/AGENTS.md index e3028b9f..aafb5ca5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,3 +72,5 @@ Use the committed scripts for cloud environments: ## Reproducibility `Dockerfile` + `apkdiff.py` verify reproducible builds: `docker build -t amber-repro --build-arg VERSION=vX.Y.Z --build-arg APK_TYPE=free-arm64-v8a .` then `docker run --rm amber-repro` (expect `APKs match!`). + +- For repository collaboration, use ngit and follow `.agents/skills/ngit/SKILL.md`. diff --git a/CLAUDE.md b/CLAUDE.md index 860158ba..738e8926 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,4 +89,6 @@ In other words, the lock controls who can open and navigate the app UI; it does | `ConnectivityService.kt` | Foreground service, network monitoring, relay reconnection | | `BiometricAuthScreen.kt` | UI-only app-launch lock (biometric/PIN); not a signing gate | | `Biometrics.kt` | Wraps `BiometricPrompt` / keyguard credential prompt | -| `SecurityScreen.kt` | Toggles `useAuth` / `usePin` and the re-prompt interval | \ No newline at end of file +| `SecurityScreen.kt` | Toggles `useAuth` / `usePin` and the re-prompt interval | + +- For repository collaboration, use ngit and follow `.claude/skills/ngit/SKILL.md`.