Commit Graph
9 Commits
Author SHA1 Message Date
Claude 5f5356c0fe fix: complete PR clone multi-value, git-status auth spoofing, CLI robustness
Addresses review findings from a merge-time audit.

- **Complete the headline multi-value `clone` fix for PRs** (was applied only to
  kind:30617). GitPullRequestEvent (1618) and GitPullRequestUpdateEvent (1619)
  carry `clone` with the same spec shape but still emitted repeated single-value
  tags and read only the first value — so the exact interop bug this branch set
  out to kill was still live for PRs, both directions (ngit keeps only the last
  repeated tag; we lost every URL after the first from ngit's multi-value tag).
  Now both emit one multi-value `["clone", …]` tag and read both forms. Verified
  on the wire + GitNip34InteropTest + CLI harness (40 checks).

- **Android git-status spoofing (GitStatusIndex)**: newest-status-wins with no
  author check meant anyone could publish a kind-1632 and make someone else's
  issue render closed. Now filter statuses to the repository owner (from the
  status's own `a` tag), declared maintainers (from the cached announcement), or
  the target item's author — matching NIP-34 and the CLI's derivation. Pre-existing
  on main; this branch made the CLI/Android divergence visible.

- **CLI robustness**: `git comment`/`git patch` no longer block forever reading
  stdin on an interactive TTY (amy is non-interactive — error instead). The local
  `git` subprocesses in `git init`/`git apply` now drain stdout on a side thread
  under a bounded `waitFor` + `destroyForcibly`, so a wedged git can't hang the
  CLI.

Left as a follow-up (cosmetic): GitBrowseCommands.candidateUrls duplicates
GitRepositoryBrowserViewModel's — worth lifting to shared code, not worth the
cross-module coupling here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
2026-07-21 03:37:58 +00:00
Claude 63d01c9287 fix(cli): audit fixes — shallow-clone euc, read truncation, process deadlock
Findings from a review pass over the git-parity branch, with fixes:

- **`git init` announced a WRONG earliest-unique-commit on shallow clones**
  (interop-critical). `git rev-list --max-parents=0 HEAD` returns the shallow
  boundary commits, not the true root, so the repo would be announced under a
  different cross-fork identity than ngit computes. Now: detect shallow clones
  and omit the euc with a warning to pass `--earliest-commit`; on full clones
  derive the deterministic `--first-parent` mainline root instead of an
  arbitrary `tail -1`.

- **`git issues|patches|prs` silently truncated and mis-derived status** on
  active repos: one single-page `drain` pulled items AND status events under a
  shared cap, so status events (newer, more numerous) could crowd items out of
  the window and the close-status that determines an item's state could fall
  outside it → a closed item read as open. Now paginate the items
  (`drainAllPages`) and fetch exactly the statuses that `e`-reference them.
  Verified on the live amethyst repo: 51 PRs paginated, 19 correctly closed.

- **Pipe-buffer deadlocks** (latent): `GitInitCommand.git()` discards stderr to
  the OS (a chatty command can no longer fill its stderr pipe and hang the
  stdout read); `GitApplyCommand.runGit()` writes stdin on a background thread
  while draining stdout, so a patch larger than the pipe buffer can't deadlock.

- Minor: `git cat` binary detection uses an index loop instead of boxing 8000
  bytes; `GitRepositoryEvent.clones()/webs()` dedupe.

The harness `git init` test now runs against a fresh full checkout (this repo's
CI checkout is shallow) and adds a shallow-clone case asserting the euc is
omitted. 38/38.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
2026-07-21 03:37:58 +00:00
Claude fa32ea50a3 test(cli): live interop check against the real ngit-published amethyst repo
Adds a `--live` block that reads the actual amethyst repository ngit publishes
to relay.ngit.dev and asserts our reader parses ngit's real multi-value `clone`
tag (currently 4 URLs) plus its published issues. This is the real-world proof
of the multi-value interop fix: the pre-fix reader would have surfaced only the
first clone URL. Opt-in (needs network + the live relay), skipped by default.

Verified manually end-to-end against the live repo: repo announcement (4 clone
URLs), issues (1621), patches (1617), pull requests (1618, with a real `closed`
status derived from ngit's status event), and a NIP-22 comment via `git thread`
all read correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
2026-07-21 03:37:58 +00:00
Claude 1a77c95531 fix(quartz): NIP-34 wire-format interop with ngit (clone/web, issue p, plain r)
Verified Amethyst's NIP-34 events byte-for-byte against the ngit reference
implementation (DanConwayDev/ngit-cli) and the spec, and fixed three real
interop divergences in quartz — so ngit/gitworkshop and Amethyst read each
other's git repos, issues, patches, and PRs without losing data.

- Repository announcement `clone`/`web` were emitted as REPEATED single-value
  tags (`["clone", a]`, `["clone", b]`). The spec and ngit use ONE multi-value
  tag (`["clone", a, b]`), and ngit's parser keeps only the LAST of repeated
  known tags — so multi-URL repos silently lost every URL but one in both
  directions. Now emitted as a single multi-value tag; `clones()`/`webs()` read
  BOTH the spec form and the legacy repeated form, so old events still parse.
  (`relays`/`maintainers` were already correct multi-value tags.)

- Issues (kind 1621) were missing the `["p", <repo-owner>]` tag that patches and
  PRs already include — a maintainer watching `#p` wouldn't see them. The
  builder now adds it (fixes both the CLI and the Android issue-creation path,
  which both passed an empty notify list).

- Patch / PR / PR-update `r` tags carried the `"euc"` marker
  (`["r", commit, "euc"]`). Per the spec and ngit that marker belongs only on
  the kind-30617 announcement; other `r` tags are plain `["r", commit]`. A `#r`
  filter matches either shape, so this is a spec-compliance/byte-parity fix.

`alt` (NIP-31) tags are intentionally still omitted — quartz treats the generic
alt client-hint as deprecated, and ngit/gitworkshop parse the structured tags,
so it isn't required for interop.

Adds `GitNip34InteropTest` (5 cases: multi-value write, tolerant read of both
forms, issue p-tag, plain patch r-tag) and 4 wire-format assertions to the CLI
git harness (37 offline). No regressions in the nip34 or Search suites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
2026-07-21 03:37:58 +00:00
Claude d3e208322c feat(cli): add amy git label (NIP-32) and amy git apply (patch → working tree)
Close two more ngit/nak parity gaps:

- `git label TARGET LABEL[,LABEL]` — attach NIP-32 kind:1985 labels to an
  issue/patch/PR (the `ngit pr label` / `issue label` surface), over quartz's
  existing `LabelEvent`. Namespace defaults to `ugc`; `--namespace` overrides.
- `git apply PATCH_ID` — fetch a kind:1617 patch and apply it to the local
  working tree via `git am` (the `nak git patch apply` / `ngit pr apply`
  surface); `--check` dry-runs `git apply --check`, `--print` emits the patch.
  Shells out to `git` like `git init`, since it operates on the local checkout.

Verified end-to-end: a patch published to a relay, fetched, and `git am`'d as a
real commit into a scratch repo; labels land as kind 1985. The harness gains 5
assertions (label + a full publish→apply round-trip), now 33 offline.

Remaining out-of-scope items are documented: git-packfile push (needs a git
write layer quartz lacks) and NIP-34 cover notes (kind 1624, no quartz builder).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
2026-07-21 03:37:58 +00:00
Claude b06184ac49 feat(cli): add amy git init — bootstrap a repo from the local git checkout
Match `ngit init` / `nak git init`: read the local git repository and publish a
NIP-34 repository announcement, deriving the fields instead of making the user
type them. Shells out to `git` to determine the name (top-level dir), clone URL
(origin remote, ssh→https normalized), earliest-unique-commit (root commit),
and — for the accompanying kind:30618 state — the branch/tag tips and HEAD.
Publishes the 30617 announcement and (unless `--no-state`) the 30618 state in
one shot. Every derived value is overridable with a flag; outside a git repo
the derivation is skipped and `--name`/`--clone` are supplied manually.

This is the one `amy git` verb that shells out to `git`, since it is inherently
about the local working tree — exactly like the tools it mirrors.

Verified against the amethyst checkout itself (derives name=amethyst, the origin
clone URL, the root commit as EUC, and a 30618 with the live branches + HEAD).
The harness gains 4 assertions driving `git init` against its own checkout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
2026-07-21 03:37:58 +00:00
Claude 45d33c016e feat(cli): add amy git browse|cat|log — read git objects over smart-HTTP
Give `amy git` the git-object read side of `nak git download` / a shallow
clone. `browse` lists a repo's tree, `cat` prints (or `--out` writes) a file at
a ref, and `log` shows recent commit history — all over the git smart-HTTP v2
protocol via quartz's `GitHttpClient` (the same shallow-clone path the Android
repo browser uses). REPO may be a NIP-34 coordinate/naddr (whose announcement
supplies the clone URL) or a raw http(s) clone URL; `--clone` and `--ref`
override the URL and branch/tag.

Read-only: pushing git objects back to clone/GRASP servers stays out of scope.

Verified live against a public repo (octocat/Hello-World) — browse/cat/log all
return correct trees, blobs, and history. The harness gains a `--live` block
(28 assertions with `--live`, 24 in the default offline run) exercising these
against `$LIVE_REPO`, skipped by default since it needs a reachable git host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
2026-07-21 03:37:57 +00:00
Claude 9922eef9f7 feat(cli): add amy git grasp list|set (NIP-34 GRASP server list, kind 10317)
Declare/read a user's preferred GRASP (Git-over-Nostr hosting) servers in
preference order — the NIP-65-style list `ngit`/`nak git` consult to decide
where PR tip branches (`refs/nostr/<pr-id>`) get pushed. `set` publishes a
kind:10317 to the outbox; `list` reads it back cache-first (anonymous-capable).
Thin assembly over quartz `UserGraspListEvent`. The git push itself stays out
of scope, as with the rest of the packfile transport.

Extends the git NIP-34 harness with a grasp round-trip (24 assertions) and
updates the README/ROADMAP/help tables.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
2026-07-21 03:37:57 +00:00
Claude ad66e6c224 feat(cli): full NIP-34 git collaboration parity for amy git
Extend `amy git` from repo announce/list/show/issue to the complete
pure-Nostr surface of `ngit` and `nak git`, so every NIP-34 collaboration
flow is scriptable without a GUI.

New sub-verbs (all thin assembly over quartz's `nip34Git` builders):

- `git state`   — kind:30618 repository state (branch/tag tips + HEAD)
- `git patch`   — kind:1617 patch from `git format-patch` (--file or stdin),
                  with --root/--root-revision, --commit, --parent-commit,
                  and --in-reply-to for revision chains
- `git pr` / `git pr-update` — kind:1618 pull request + kind:1619 tip update
- `git comment` — NIP-22 kind:1111 reply on an issue/patch/PR/repo (the
                  modern replacement for the deprecated kind:1622 git reply)
- `git open|applied|close|draft` — kind:1630/1631/1632/1633 status events
                  (aliases `merged`/`resolved` for applied); applied carries
                  --merge-commit / --commit / --patch
- `git issues|patches|prs` — list a repo's items with status derived from the
                  newest authoritative (owner/maintainer/author) status event,
                  with --open/--applied/--closed/--draft/--status filters
- `git thread`  — one item plus its status timeline and comments

Shared parsing/fetch/routing glue lives in `GitSupport`; the existing
announce/list/show/issue verbs now reuse it. The git *packfile* transport
(clone/fetch/push of real objects to clone/GRASP servers) stays out of
scope — it needs a git plumbing layer, not an event builder — and is
documented as such.

Adds `cli/tests/git/git-nip34-headless.sh` (21 assertions, drives the whole
flow against `amy serve` and checks the status-deriving reads) and updates
the README/ROADMAP command tables.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
2026-07-21 03:37:57 +00:00