diff --git a/docs/01.md b/docs/01.md index eb9f60c..63545d1 100644 --- a/docs/01.md +++ b/docs/01.md @@ -176,3 +176,21 @@ This NIP defines no rules for how `NOTICE` messages should be sent or treated. * `["CLOSED", "sub1", "error: could not connect to the database"]` * `["CLOSED", "sub1", "error: shutting down idle subscription"]` - The standardized machine-readable prefixes for `OK` and `CLOSED` are: `duplicate`, `pow`, `blocked`, `rate-limited`, `invalid`, `restricted`, `mute` and `error` for when none of that fits. + +## GitRepublic Usage + +GitRepublic uses NIP-01 as the foundation for all Nostr event handling. All events follow the standard event structure defined in NIP-01. + +### Event Structure + +All repository-related events (announcements, PRs, issues, patches, etc.) follow the NIP-01 event format: +- Events are serialized according to NIP-01 rules (UTF-8, no whitespace, proper escaping) +- Event IDs are computed as SHA256 of the serialized event +- Signatures use Schnorr signatures on secp256k1 +- The `nostr-tools` library is used for event serialization, ID computation, and signature verification + +### Kind 1 (Text Note) Usage + +GitRepublic uses kind 1 events as a fallback mechanism for relay write proofs when NIP-98 authentication events are not available. This ensures git operations can still be authenticated even if the NIP-98 flow fails. + +**Implementation**: `src/lib/services/nostr/nostr-client.ts`, `src/lib/types/nostr.ts` diff --git a/docs/02.md b/docs/02.md index 8354bf0..8c71dfe 100644 --- a/docs/02.md +++ b/docs/02.md @@ -76,3 +76,98 @@ and another from `a8bb3d884d5d90b413d9891fe4c4e46d` that says When the user sees `21df6d143fb96c2ec9d63726bf9edc71` the client can show _erin_ instead; When the user sees `a8bb3d884d5d90b413d9891fe4c4e46d` the client can show _david.erin_ instead; When the user sees `f57f54057d2a7af0efecc8b0b66f5708` the client can show _frank.david.erin_ instead. + +## GitRepublic Usage + +GitRepublic uses KIND 3 (Contact List) events to enable repository filtering based on social connections. + +### Repository Filtering + +When a user enables the "Show only my repos and those of my contacts" filter on the landing page, GitRepublic: + +1. **Fetches the user's contact list**: Retrieves the latest KIND 3 event published by the logged-in user +2. **Extracts contact pubkeys**: Parses all `p` tags from the contact list event to build a set of followed pubkeys +3. **Includes the user's own pubkey**: Automatically adds the logged-in user's pubkey to the filter set +4. **Filters repositories**: Shows only repositories where: + - The repository owner (event `pubkey`) is in the contact set, OR + - Any maintainer (from `maintainers` tags) is in the contact set + +### Implementation Details + +- **Pubkey normalization**: GitRepublic handles both hex-encoded pubkeys and bech32-encoded npubs in contact lists +- **Maintainer matching**: The filter checks all maintainers listed in repository announcement `maintainers` tags +- **Real-time updates**: The contact list is fetched when the user logs in and can be refreshed by reloading the page + +### Example Use Case + +A user follows several developers on Nostr. By enabling the contact filter: + +- They see repositories owned by people they follow +- They see repositories where their contacts are maintainers +- They see their own repositories +- They don't see repositories from people they don't follow + +This creates a personalized, social discovery experience for finding relevant code repositories based on trust relationships established through Nostr's follow mechanism. + +### Technical Implementation + +```typescript +// Fetch user's kind 3 contact list +const contactEvents = await nostrClient.fetchEvents([ + { + kinds: [KIND.CONTACT_LIST], // KIND 3 + authors: [userPubkey], + limit: 1 + } +]); + +// Extract pubkeys from 'p' tags +const contactPubkeys = new Set(); +contactPubkeys.add(userPubkey); // Include user's own repos + +if (contactEvents.length > 0) { + const contactEvent = contactEvents[0]; + for (const tag of contactEvent.tags) { + if (tag[0] === 'p' && tag[1]) { + let pubkey = tag[1]; + // Decode npub if needed + try { + const decoded = nip19.decode(pubkey); + if (decoded.type === 'npub') { + pubkey = decoded.data as string; + } + } catch { + // Assume it's already a hex pubkey + } + contactPubkeys.add(pubkey); + } + } +} + +// Filter repositories +const filteredRepos = allRepos.filter(event => { + // Check if owner is in contacts + if (contactPubkeys.has(event.pubkey)) return true; + + // Check if any maintainer is in contacts + const maintainerTags = event.tags.filter(t => t[0] === 'maintainers'); + for (const tag of maintainerTags) { + for (let i = 1; i < tag.length; i++) { + let maintainerPubkey = tag[i]; + // Decode npub if needed + try { + const decoded = nip19.decode(maintainerPubkey); + if (decoded.type === 'npub') { + maintainerPubkey = decoded.data as string; + } + } catch { + // Assume it's already a hex pubkey + } + if (contactPubkeys.has(maintainerPubkey)) return true; + } + } + return false; +}); +``` + +This implementation provides a seamless way for users to discover repositories from their social network while maintaining the decentralized, trustless nature of Nostr. diff --git a/docs/07.md b/docs/07.md index 294acd3..c96f878 100644 --- a/docs/07.md +++ b/docs/07.md @@ -30,3 +30,29 @@ To make sure that the `window.nostr` is available to nostr clients on page load, ### Implementation See https://github.com/aljazceru/awesome-nostr#nip-07-browser-extensions. + +## GitRepublic Usage + +NIP-07 is the primary authentication method for GitRepublic. All user interactions that require signing events use the NIP-07 browser extension interface. + +### Authentication Flow + +1. **Availability Check**: GitRepublic checks for `window.nostr` availability on page load +2. **Public Key Retrieval**: When users need to authenticate, `getPublicKey()` is called to get their pubkey +3. **Event Signing**: All repository announcements, PRs, issues, and other events are signed using `signEvent()` + +### Key Features + +- **Repository Creation**: Users sign repository announcement events (kind 30617) using NIP-07 +- **Repository Updates**: Settings changes, maintainer additions, and other updates are signed via NIP-07 +- **Pull Requests**: PR creation and updates are signed by the PR author +- **Issues**: Issue creation and comments are signed by the author +- **Commit Signatures**: Git commits can be signed using NIP-07 (client-side only, keys never leave browser) + +### Security + +- Keys never leave the browser - all signing happens client-side +- No server-side key storage required +- Users maintain full control of their private keys + +**Implementation**: `src/lib/services/nostr/nip07-signer.ts` diff --git a/docs/09.md b/docs/09.md index f061464..7000c48 100644 --- a/docs/09.md +++ b/docs/09.md @@ -51,3 +51,26 @@ Relays MAY validate that a deletion request event only references events that ha ## Deletion Request of a Deletion Request Publishing a deletion request event against a deletion request has no effect. Clients and relays are not obliged to support "unrequest deletion" functionality. + +## GitRepublic Usage + +GitRepublic uses NIP-09 deletion requests for repository fork failures and other error scenarios where an event needs to be removed. + +### Fork Failure Deletion + +When a repository fork operation fails (e.g., ownership transfer event cannot be published), GitRepublic creates a deletion request to remove the failed fork announcement: + +```typescript +{ + kind: 5, + tags: [ + ['a', `30617:${userPubkeyHex}:${forkRepoName}`], + ['k', KIND.REPO_ANNOUNCEMENT.toString()] + ], + content: 'Fork failed: ownership transfer event could not be published...' +} +``` + +This ensures that failed fork attempts don't leave orphaned repository announcements in the system. + +**Implementation**: `src/routes/api/repos/[npub]/[repo]/fork/+server.ts` diff --git a/docs/10.md b/docs/10.md index 9fd4415..1484545 100644 --- a/docs/10.md +++ b/docs/10.md @@ -81,4 +81,27 @@ Where: * Many "e" tags: `["e", ]` `["e", ]`, ..., `["e", ]`
There may be any number of ``. These are the ids of events which may, or may not be in the reply chain. -They are citing from this event. `root-id` and `reply-id` are as above. \ No newline at end of file +They are citing from this event. `root-id` and `reply-id` are as above. + +## GitRepublic Usage + +GitRepublic uses NIP-10 event references primarily in patch events (kind 1617) to create patch series. Patches in a patch set use `e` tags with `reply` markers to link to previous patches in the series. + +### Patch Series Threading + +When multiple patches are sent as part of a patch series, each patch after the first includes an `e` tag with a `reply` marker pointing to the previous patch: + +```jsonc +{ + "kind": 1617, + "tags": [ + ["a", "30617:owner:repo"], + ["e", "previous_patch_event_id", "wss://relay.example.com", "reply"], + // ... other tags + ] +} +``` + +This creates a threaded chain of patches that can be applied in sequence. + +**Implementation**: Used in patch creation and processing throughout the codebase \ No newline at end of file diff --git a/docs/19.md b/docs/19.md index 4787ecd..72799d1 100644 --- a/docs/19.md +++ b/docs/19.md @@ -67,3 +67,29 @@ These possible standardized `TLV` types are indicated here: - `npub` keys MUST NOT be used in NIP-01 events or in NIP-05 JSON responses, only the hex format is supported there. - When decoding a bech32-formatted string, TLVs that are not recognized or supported should be ignored, rather than causing an error. + +## GitRepublic Usage + +GitRepublic extensively uses NIP-19 bech32 encoding for user-friendly display and input of Nostr entities throughout the application. + +### User Interface + +- **Repository URLs**: Repositories are accessed via URLs like `/repos/{npub}/{repo-name}` where `npub` is the bech32-encoded public key +- **User Profiles**: User profile pages use npub in URLs: `/users/{npub}` +- **Search**: Users can search for repositories using npub, naddr, or nevent formats +- **Repository References**: The "Load Existing Repository" feature accepts hex event IDs, nevent, or naddr formats + +### Internal Handling + +- **Conversion**: All npub values are converted to hex format internally for event creation and filtering +- **Maintainers**: Maintainer lists accept both npub and hex formats, converting as needed +- **Contact Lists**: When processing kind 3 contact lists, GitRepublic handles both npub and hex pubkey formats + +### Search Support + +The repository search feature can decode and search by: +- **npub**: User public keys (converted to hex for filtering) +- **naddr**: Repository announcement addresses (decoded to extract pubkey, kind, and d-tag) +- **nevent**: Event references (decoded to extract event ID) + +**Implementation**: `src/lib/services/nostr/nip19-utils.ts`, used throughout the codebase for encoding/decoding diff --git a/docs/22.md b/docs/22.md index f356fa2..4eb4f1f 100644 --- a/docs/22.md +++ b/docs/22.md @@ -198,3 +198,40 @@ A reply to a podcast comment: // other fields } ``` + +## GitRepublic Usage + +GitRepublic uses NIP-22 comments for threaded discussions on pull requests, issues, and patches. Comments enable collaborative code review and issue discussion. + +### Comment Threading + +Comments on PRs, issues, and patches use NIP-22's uppercase/lowercase tag convention: +- **Uppercase tags** (`E`, `K`, `P`, `A`) reference the root event (PR, issue, or patch) +- **Lowercase tags** (`e`, `k`, `p`, `a`) reference the parent comment (for reply threads) + +### Implementation Details + +- **Root Comments**: Comments directly on a PR/issue use uppercase tags pointing to the root event +- **Reply Comments**: Replies to other comments use lowercase tags for the parent comment +- **Relay Hints**: Relay URLs are included in `E` and `e` tags to help clients locate events +- **Plaintext Content**: Comment content is plaintext (no HTML/Markdown) as per NIP-22 spec + +### Example: Comment on Pull Request + +```jsonc +{ + "kind": 1111, + "content": "This looks good, but consider adding error handling here.", + "tags": [ + ["E", "pr_event_id", "wss://relay.example.com", "pr_author_pubkey"], + ["K", "1618"], + ["P", "pr_author_pubkey", "wss://relay.example.com"], + ["e", "pr_event_id", "wss://relay.example.com", "pr_author_pubkey"], + ["k", "1618"], + ["p", "pr_author_pubkey", "wss://relay.example.com"] + ] +} +``` + +**Implementation**: `src/lib/services/nostr/highlights-service.ts` (comment creation), used throughout PR and issue discussion features +``` diff --git a/docs/34.md b/docs/34.md index d35c0eb..602ed92 100644 --- a/docs/34.md +++ b/docs/34.md @@ -246,3 +246,45 @@ The event SHOULD include a list of `g` tags with grasp service websocket URLs in ## Possible things to be added later - inline file comments kind (we probably need one for patches and a different one for merged files) + +## GitRepublic Usage + +NIP-34 is the core NIP that GitRepublic implements. All repository functionality is built on top of NIP-34 event kinds. + +### Repository Announcements (Kind 30617) + +GitRepublic uses repository announcements to: +- **Discover Repositories**: The landing page fetches all kind 30617 events to display available repositories +- **Repository Creation**: Users create repository announcements via the signup page +- **Repository Updates**: Settings changes update the repository announcement event +- **Fork Detection**: Forks are identified by `a` tags pointing to the original repository + +### Pull Requests (Kind 1618) + +- **PR Creation**: Users create PRs by pushing commits and creating kind 1618 events +- **PR Updates**: PR updates (kind 1619) change the tip commit of existing PRs +- **PR Display**: PRs are displayed with their full markdown content and metadata +- **PR Merging**: Merging a PR creates a status event (kind 1631) with merge commit information + +### Issues (Kind 1621) + +- **Issue Creation**: Users create issues for bug reports, feature requests, and questions +- **Issue Threading**: Issues use NIP-22 comments for discussion +- **Issue Status**: Status events (kinds 1630-1633) track issue state (open, resolved, closed, draft) + +### Status Events (Kinds 1630-1633) + +- **Status Tracking**: PRs and issues use status events to track their state +- **Most Recent Wins**: Only the most recent status event from the author or a maintainer is considered valid +- **Merge Tracking**: When a PR is merged (status 1631), the merge commit ID is included in tags + +### Repository State (Kind 30618) + +- **Branch Tracking**: Optional repository state events track branch and tag positions +- **State Updates**: Repository state can be updated to reflect current branch/tag positions + +**Implementation**: +- Repository announcements: `src/routes/signup/+page.svelte`, `src/lib/services/nostr/repo-polling.ts` +- Pull requests: `src/lib/services/nostr/prs-service.ts` +- Issues: `src/lib/services/nostr/issues-service.ts` +- Status events: Used throughout PR and issue management diff --git a/docs/65.md b/docs/65.md index 36f0d12..d240d70 100644 --- a/docs/65.md +++ b/docs/65.md @@ -41,3 +41,33 @@ Clients SHOULD guide users to keep `kind:10002` lists small (2-4 relays of each ### Discoverability Clients SHOULD spread an author's `kind:10002` event to as many relays as viable, paying attention to relays that, at any moment, serve naturally as well-known public indexers for these relay lists (where most other clients and users are connecting to in order to publish and fetch those). + +## GitRepublic Usage + +GitRepublic uses NIP-65 relay lists to discover user's preferred relays for publishing events. This ensures events are published to relays the user trusts and uses. + +### Relay Discovery + +When publishing repository announcements, PRs, issues, or other events: + +1. **Fetch User's Relay List**: GitRepublic fetches the user's kind 10002 event +2. **Extract Write Relays**: Parses `r` tags with `write` marker or no marker (both read and write) +3. **Combine with Defaults**: Combines user's outbox relays with default relays for redundancy +4. **Publish**: Sends events to the combined relay list + +### Fallback to Kind 3 + +If no kind 10002 event is found, GitRepublic falls back to kind 3 (contact list) for relay discovery, maintaining compatibility with older clients. + +### Implementation + +```typescript +// Fetch user's relay preferences +const { inbox, outbox } = await getUserRelays(pubkey, nostrClient); +const userRelays = combineRelays(outbox); + +// Publish to user's preferred relays +await nostrClient.publishEvent(signedEvent, userRelays); +``` + +**Implementation**: `src/lib/services/nostr/user-relays.ts`, used when publishing all repository-related events diff --git a/docs/84.md b/docs/84.md index 1094f55..4a6fc76 100644 --- a/docs/84.md +++ b/docs/84.md @@ -49,3 +49,47 @@ This is to prevent the creation and multiple notes (highlight + kind 1) for a si p-tag mentions MUST have a `mention` attribute to distinguish it from authors and editors. r-tag urls from the comment MUST have a `mention` attribute to distinguish from the highlighted source url. The source url MUST have the `source` attribute. + +## GitRepublic Usage + +GitRepublic extends NIP-84 highlights for code review and code selection features. Highlights enable users to select and comment on specific code sections in pull requests. + +### Code Highlighting + +When users select code in a PR for review: + +1. **Highlight Creation**: A kind 9802 event is created with the selected code in the `content` field +2. **File Context**: Custom tags (`file`, `line-start`, `line-end`) specify which file and lines are highlighted +3. **PR Reference**: The highlight references the PR using `a` and `e` tags +4. **Attribution**: `P` and `p` tags reference the PR author and highlight creator + +### Extended Tags + +GitRepublic extends NIP-84 with file-specific tags for code context: +- `file`: File path being highlighted +- `line-start`: Starting line number +- `line-end`: Ending line number +- `context`: Optional surrounding context +- `comment`: Optional comment text for quote highlights + +### Example: Code Highlight on PR + +```jsonc +{ + "kind": 9802, + "content": "const result = await fetch(url);", + "tags": [ + ["a", "1618:pr_author_pubkey.../repo-name"], + ["e", "pr_event_id"], + ["P", "pr_author_pubkey"], + ["K", "1618"], + ["file", "src/main.ts"], + ["line-start", "42"], + ["line-end", "45"], + ["context", "// Fetch data from API"], + ["p", "pr_author_pubkey", "wss://relay.example.com", "author"] + ] +} +``` + +**Implementation**: `src/lib/services/nostr/highlights-service.ts` diff --git a/docs/98.md b/docs/98.md index be425b2..96e8f8f 100644 --- a/docs/98.md +++ b/docs/98.md @@ -61,3 +61,47 @@ eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3 ## Reference Implementations - C# ASP.NET `AuthenticationHandler` [NostrAuth.cs](https://gist.github.com/v0l/74346ae530896115bfe2504c8cd018d3) + +## GitRepublic Usage + +NIP-98 is used extensively in GitRepublic for authenticating git operations (clone, push, pull) and API requests. This enables secure git operations without requiring traditional username/password authentication. + +### Git Operations Authentication + +All git operations (push, pull, clone) use NIP-98 authentication: + +1. **Client Creates Auth Event**: User's browser extension creates a kind 27235 event with: + - `u` tag: Absolute URL of the git endpoint + - `method` tag: HTTP method (POST for push, GET for pull/clone) + - `payload` tag: SHA256 hash of request body (for POST requests) + +2. **Base64 Encoding**: The event is base64-encoded and sent in `Authorization: Nostr {base64_event}` header + +3. **Server Verification**: GitRepublic verifies: + - Event kind is 27235 + - Timestamp is within 60 seconds + - URL matches exactly (normalized, trailing slashes removed) + - HTTP method matches + - Payload hash matches (for POST requests) + - Event signature is valid + +### API Endpoint Authentication + +API endpoints that modify repository state also use NIP-98: +- File creation/editing +- Repository settings updates +- PR/issue creation +- Comment posting + +### URL Normalization + +GitRepublic normalizes URLs before comparison to handle trailing slashes and ensure consistent matching: +- Removes trailing slashes +- Preserves query parameters +- Handles both HTTP and HTTPS + +### Fallback to Kind 1 + +If NIP-98 authentication fails, GitRepublic can fall back to kind 1 (text note) events for relay write proofs, though this is less secure and not recommended. + +**Implementation**: `src/lib/services/nostr/nip98-auth.ts`, used in all git operation endpoints and API routes diff --git a/docs/CustomKinds.md b/docs/CustomKinds.md new file mode 100644 index 0000000..7b96e5f --- /dev/null +++ b/docs/CustomKinds.md @@ -0,0 +1,237 @@ +# Custom Event Kinds + +This document describes the custom event kinds used by GitRepublic that are not part of any standard NIP. These may be proposed as NIPs in the future. + +## Kind 1640: Commit Signature + +**Status**: Custom implementation (not in any NIP) + +Git commit signature events are used to cryptographically sign git commits using Nostr keys. This provides cryptographic proof that a commit was made by a specific Nostr user. + +### Event Structure + +```jsonc +{ + "kind": 1640, + "pubkey": "committer_pubkey_hex...", + "created_at": 1234567890, + "content": "Signed commit: \n\n", + "tags": [ + ["commit", "abc123def456..."], // Final commit hash (added after commit is created) + ["author", "John Doe"], // Author name + ["author", "john@example.com"], // Author email (second author tag) + ["message", "Fix bug in feature"], // Commit message + ["e", "nip98_auth_event_id"] // Optional: Reference to NIP-98 auth event + ], + "id": "...", + "sig": "..." +} +``` + +### Tag Descriptions + +- **`commit`** (required): The final commit hash after the commit is created. This tag is added after the commit is created, as the hash is not known beforehand. +- **`author`** (required, appears twice): First occurrence contains the author name, second contains the author email. +- **`message`** (required): The commit message text. +- **`e`** (optional): Reference to a NIP-98 authentication event if the commit was made via HTTP git operations. + +### Usage in GitRepublic + +1. **Client-Side Signing**: When users make commits through the web interface, they can sign commits using NIP-07 (browser extension). The signature event is created client-side and keys never leave the browser. + +2. **Server-Side Signing**: For git operations (push via git client), commits can be signed using NIP-98 authentication events. The commit signature event references the NIP-98 event. + +3. **Commit Message Embedding**: The signature is embedded in the git commit message as a trailer: + ``` + Nostr-Signature: + ``` + +4. **Verification**: Commit signatures can be verified by: + - Checking the event signature + - Verifying the commit hash matches + - Confirming the author information matches the commit + +### Rationale + +Using a dedicated kind (1640) instead of kind 1 (text note) prevents spamming the user's feed with commit signatures. It also provides a clear, searchable way to find all commits signed by a specific user. + +**Implementation**: `src/lib/services/git/commit-signer.ts` + +--- + +## Kind 1641: Ownership Transfer + +**Status**: Custom implementation (not in any NIP) + +Repository ownership transfer events enable transferring repository ownership from one pubkey to another. This is a **non-replaceable event** to maintain an immutable chain of ownership. + +### Event Structure + +#### Regular Ownership Transfer + +```jsonc +{ + "kind": 1641, + "pubkey": "old_owner_pubkey_hex...", + "created_at": 1234567890, + "content": "Transferring ownership of repository my-repo to new maintainer", + "tags": [ + ["a", "30617:old_owner_pubkey.../my-repo"], // Repository address + ["p", "new_owner_pubkey_hex..."], // New owner pubkey (hex or npub) + ["d", "my-repo"] // Repository identifier + ], + "id": "...", + "sig": "..." +} +``` + +#### Self-Transfer (Initial Ownership Proof) + +```jsonc +{ + "kind": 1641, + "pubkey": "owner_pubkey_hex...", + "created_at": 1234567890, + "content": "Initial ownership proof for repository my-repo", + "tags": [ + ["a", "30617:owner_pubkey.../my-repo"], + ["p", "owner_pubkey_hex..."], // Same as pubkey (self-transfer) + ["d", "my-repo"], + ["t", "self-transfer"] // Marker for initial ownership proof + ], + "id": "...", + "sig": "..." +} +``` + +### Tag Descriptions + +- **`a`** (required): Repository address in format `30617::` +- **`p`** (required): New owner pubkey (can be hex or npub format). For self-transfers, this is the same as the event `pubkey`. +- **`d`** (required): Repository identifier (d-tag from repository announcement) +- **`t`** (optional): `"self-transfer"` marker for initial ownership proofs + +### Usage in GitRepublic + +1. **Initial Ownership**: When a repository is first created, a self-transfer event (owner → owner) is published to establish initial ownership proof. This creates an immutable record that the owner created the repository. + +2. **Ownership Transfers**: When transferring ownership to another user: + - The current owner creates a kind 1641 event with the new owner's pubkey + - The new owner must accept the transfer (future enhancement) + - The transfer creates an immutable chain of ownership + +3. **Ownership Verification**: To verify current ownership: + - Find the repository announcement (kind 30617) + - Find all ownership transfer events for that repository + - Follow the chain from the initial self-transfer to the most recent transfer + - The most recent transfer's `p` tag indicates the current owner + +4. **Fork Operations**: When forking a repository, a self-transfer event is created to prove the fork owner's claim to the forked repository. + +### Rationale + +NIP-34 doesn't define ownership transfers. This custom kind provides: +- **Immutability**: Non-replaceable events create an unchangeable chain +- **Auditability**: Full history of ownership changes +- **Security**: Cryptographic proof of ownership transfers +- **Fork Integrity**: Ensures forks can be traced back to their origin + +**Implementation**: `src/lib/services/nostr/ownership-transfer-service.ts` + +--- + +## Kind 30620: Branch Protection + +**Status**: Custom implementation (not in any NIP) + +Branch protection rules allow repository owners to enforce policies on specific branches, such as requiring pull requests, reviewers, or status checks before merging. + +### Event Structure + +```jsonc +{ + "kind": 30620, + "pubkey": "owner_pubkey_hex...", + "created_at": 1234567890, + "content": "", + "tags": [ + ["d", "my-repo"], // Repository name + ["a", "30617:owner_pubkey.../my-repo"], // Repository address + + // Branch: main + ["branch", "main"], // Branch name + ["branch", "main", "require-pr"], // Require pull request (no value) + ["branch", "main", "require-reviewers", "npub1reviewer..."], // Required reviewer + ["branch", "main", "require-reviewers", "npub2reviewer..."], // Another reviewer + ["branch", "main", "require-status", "ci"], // Required status check + ["branch", "main", "require-status", "lint"], // Another required status + + // Branch: develop + ["branch", "develop"], + ["branch", "develop", "require-pr"], + ["branch", "develop", "allow-force-push"], // Allow force push + ["branch", "develop", "allowed-maintainers", "npub1maintainer..."] // Can bypass protection + ], + "id": "...", + "sig": "..." +} +``` + +### Tag Descriptions + +- **`d`** (required): Repository name/identifier +- **`a`** (required): Repository address in format `30617::` +- **`branch`** (required, appears multiple times): Branch name and protection settings + - `["branch", ""]`: Declares a protected branch + - `["branch", "", "require-pr"]`: Requires pull request before merging + - `["branch", "", "allow-force-push"]`: Allows force push to this branch + - `["branch", "", "require-reviewers", ""]`: Required reviewer (can appear multiple times) + - `["branch", "", "require-status", ""]`: Required status check (can appear multiple times) + - `["branch", "", "allowed-maintainers", ""]`: Maintainer who can bypass protection (can appear multiple times) + +### Protection Rules + +1. **Require Pull Request**: Direct pushes to protected branches are blocked. All changes must come through pull requests. + +2. **Require Reviewers**: Pull requests to protected branches must be approved by at least one of the specified reviewers. + +3. **Require Status Checks**: All specified status checks must pass before a PR can be merged. + +4. **Allow Force Push**: By default, force pushes are blocked on protected branches. This tag allows them. + +5. **Allowed Maintainers**: Specified maintainers can bypass protection rules (e.g., for emergency fixes). + +### Usage in GitRepublic + +1. **Setting Protection**: Repository owners create/update branch protection rules by publishing a kind 30620 event. + +2. **Enforcement**: When users attempt to: + - Push directly to a protected branch → Blocked if `require-pr` is set + - Merge a PR to a protected branch → Requires reviewers and status checks if specified + - Force push → Blocked unless `allow-force-push` is set or user is an allowed maintainer + +3. **Replaceable Events**: Branch protection events are replaceable (same `d` tag). Publishing a new event replaces all previous rules. + +4. **Access Control**: Only the repository owner can create/update branch protection rules. + +### Rationale + +NIP-34 doesn't define branch protection. This custom kind provides: +- **Code Quality**: Enforces code review and testing before merging +- **Security**: Prevents direct pushes to critical branches +- **Flexibility**: Configurable rules per branch +- **Maintainability**: Clear, auditable protection rules + +**Implementation**: `src/lib/services/nostr/branch-protection-service.ts` (if implemented), referenced in repository settings + +--- + +## Summary + +| Kind | Name | Replaceable | Purpose | +|------|------|-------------|---------| +| 1640 | Commit Signature | No | Cryptographically sign git commits | +| 1641 | Ownership Transfer | No | Transfer repository ownership (immutable chain) | +| 30620 | Branch Protection | Yes | Enforce branch protection rules | + +These custom kinds extend NIP-34's git collaboration features with additional functionality needed for a production git hosting platform. They may be proposed as NIPs in the future to standardize these features across the Nostr ecosystem. diff --git a/docs/NIP_COMPLIANCE.md b/docs/NIP_COMPLIANCE.md index 66b86d2..d3d7269 100644 --- a/docs/NIP_COMPLIANCE.md +++ b/docs/NIP_COMPLIANCE.md @@ -1,1073 +1,118 @@ -# NIP Compliance Verification +# NIP Compliance and Documentation Index -This document verifies that our implementation correctly follows the Nostr Improvement Proposals (NIPs) we use. +This document serves as an index to all Nostr Improvement Proposals (NIPs) used by GitRepublic and their implementation details. -## NIPs Used +## Standard NIPs -1. **NIP-01**: Basic protocol flow (event structure, signatures) -2. **NIP-02**: Contact list (kind 3) - for relay discovery -3. **NIP-07**: Browser extension authentication (`window.nostr`) -4. **NIP-09**: Event deletion requests (kind 5) -5. **NIP-10**: Event references (e, p tags) - used in threading -6. **NIP-19**: bech32-encoded entities (npub, nsec, note, nevent, naddr) -7. **NIP-22**: Comments (kind 1111) -8. **NIP-34**: Git repository announcements and collaboration (kinds 30617, 30618, 1617, 1618, 1619, 1621, 1630-1633) -9. **NIP-65**: Relay list metadata (kind 10002) -10. **NIP-84**: Highlights (kind 9802) -11. **NIP-98**: HTTP authentication (kind 27235) +GitRepublic implements the following standard NIPs: -## Custom Event Kinds - -These are not part of any NIP but are used by this application: - -- **1640** (`COMMIT_SIGNATURE`): Git commit signature events -- **1641** (`OWNERSHIP_TRANSFER`): Repository ownership transfer events (non-replaceable) -- **30620** (`BRANCH_PROTECTION`): Branch protection rules - -## Compliance Check - -### ✅ NIP-01: Basic Protocol - -**Status**: Compliant - -- Events follow the standard structure (id, pubkey, created_at, kind, tags, content, sig) -- Signatures use Schnorr signatures on secp256k1 -- Event IDs are SHA256 of serialized event data -- Using `nostr-tools` library for verification - -**Implementation**: `src/lib/types/nostr.ts`, `src/lib/services/nostr/nostr-client.ts` - -### ✅ NIP-02: Contact List (Kind 3) - -**Status**: Compliant - -- Used for relay discovery fallback -- Fetches kind 3 events to find user's relays -- Tags format: `["p", , , ]` - -**Implementation**: `src/lib/services/nostr/user-relays.ts` - -### ✅ NIP-07: Browser Extension - -**Status**: Compliant - -- Checks for `window.nostr` availability -- Uses `getPublicKey()` and `signEvent()` methods -- Properly handles async operations -- Keys never leave the browser - -**Implementation**: `src/lib/services/nostr/nip07-signer.ts` - -### ✅ NIP-09: Event Deletion (Kind 5) - -**Status**: Compliant - -- Uses kind 5 for deletion requests -- Includes `a` tag for replaceable events (repo announcements) -- Includes `k` tag for the kind being deleted -- Content field contains reason for deletion - -**Example from code**: -```typescript -{ - kind: 5, - tags: [ - ['a', `30617:${userPubkeyHex}:${forkRepoName}`], - ['k', KIND.REPO_ANNOUNCEMENT.toString()] - ], - content: 'Fork failed: ownership transfer event could not be published...' -} -``` - -**Implementation**: `src/routes/api/repos/[npub]/[repo]/fork/+server.ts:281-289` - -### ✅ NIP-10: Event References - -**Status**: Compliant - -- Uses `e` tags for event references in PRs, issues, patches -- Uses `p` tags for pubkey references -- Properly marks `e` tags with markers (`root`, `reply`) in status events - -**Implementation**: Used throughout PR/Issue/Status event creation - -### ✅ NIP-19: bech32 Encoding - -**Status**: Compliant - -- Accepts both bech32 (npub, nsec) and hex formats -- Uses `nip19.decode()` and `nip19.npubEncode()` from `nostr-tools` -- Converts between formats as needed -- Stores keys in hex format internally - -**Implementation**: `src/lib/services/git/commit-signer.ts`, `src/lib/services/nostr/ownership-transfer-service.ts` - -### ✅ NIP-22: Comments (Kind 1111) - -**Status**: Compliant - -- Uses kind 1111 for comments -- Uses uppercase tags for root scope: `E`, `K`, `P`, `A` -- Uses lowercase tags for parent: `e`, `k`, `p`, `a` -- Content is plaintext (no HTML/Markdown) - -**Example from code**: -```typescript -tags: [ - ['E', rootEventId, '', rootPubkey], // Root event - ['K', rootEventKind.toString()], // Root kind - ['P', rootPubkey], // Root author - ['e', parentEventId, '', parentPubkey], // Parent event - ['k', parentEventKind.toString()], // Parent kind - ['p', parentPubkey] // Parent author -] -``` - -**Implementation**: `src/lib/services/nostr/highlights-service.ts:341-383` - -### ✅ NIP-34: Git Repository Announcements - -**Status**: Compliant - -#### Repository Announcements (Kind 30617) - -- ✅ Uses kind 30617 -- ✅ Includes `d` tag (repo identifier) - REQUIRED -- ✅ Includes optional tags: `name`, `description`, `clone`, `web`, `relays`, `maintainers`, `image`, `banner`, `private` -- ✅ Uses `r` tag with `euc` marker for earliest unique commit -- ✅ Supports fork identification with `a` tag pointing to original repo -- ✅ Uses `t` tag for labels (including `fork`, `private`) - -**Implementation**: `src/lib/services/nostr/repo-polling.ts`, `src/routes/api/repos/[npub]/[repo]/fork/+server.ts` - -#### Pull Requests (Kind 1618) - -- ✅ Uses kind 1618 -- ✅ Includes `a` tag with repo address: `30617::` -- ✅ Includes `subject` tag for PR title -- ✅ Includes `c` tag for commit ID (tip of PR branch) -- ✅ Includes `clone` tag with at least one clone URL -- ✅ Includes optional `branch-name` tag -- ✅ Includes optional `t` tags for labels -- ✅ Includes `p` tag for repository owner - -**Implementation**: `src/lib/services/nostr/prs-service.ts:91-134` - -#### Issues (Kind 1621) - -- ✅ Uses kind 1621 -- ✅ Includes `a` tag with repo address -- ✅ Includes `subject` tag for issue title -- ✅ Includes `p` tag for repository owner -- ✅ Includes optional `t` tags for labels -- ✅ Content is markdown text - -**Implementation**: `src/lib/services/nostr/issues-service.ts:104-138` - -#### Status Events (Kinds 1630-1633) - -- ✅ Uses kind 1630 for Open -- ✅ Uses kind 1631 for Applied/Merged -- ✅ Uses kind 1632 for Closed -- ✅ Uses kind 1633 for Draft -- ✅ Includes `e` tag with `root` marker pointing to PR/Issue -- ✅ Includes `p` tags for repository owner and event authors -- ✅ Includes optional `a` tag for repo address -- ✅ For merged PRs, includes `merge-commit` and `r` tags - -**Implementation**: `src/lib/services/nostr/prs-service.ts:139-191`, `src/lib/services/nostr/issues-service.ts:143-189` - -**Note**: We correctly use `STATUS_APPLIED` (1631) for both "merged" PRs and "resolved" issues, as per NIP-34 spec. - -### ✅ NIP-65: Relay List Metadata (Kind 10002) - -**Status**: Compliant - -- Fetches kind 10002 events for user's relay preferences -- Parses `r` tags with optional `read`/`write` markers -- Falls back to kind 3 (contact list) for older clients -- Uses write relays for publishing, read relays for mentions - -**Implementation**: `src/lib/services/nostr/user-relays.ts` - -### ✅ NIP-84: Highlights (Kind 9802) - -**Status**: Compliant - -- Uses kind 9802 for highlights -- Content contains the highlighted text -- Uses `a` or `e` tags for nostr event sources -- Uses `r` tags for URL sources -- Uses `p` tags for attribution (with optional role) -- Includes custom tags for file context: `file`, `line-start`, `line-end`, `context`, `comment` - -**Example from code**: -```typescript -tags: [ - ['a', prAddress], // PR event address - ['e', prId], // PR event ID - ['P', prAuthor], // PR author - ['K', KIND.PULL_REQUEST.toString()], // Root kind - ['file', filePath], // File being highlighted - ['line-start', lineStart.toString()], - ['line-end', lineEnd.toString()], - ['context', context], // Optional context - ['comment', comment] // Optional comment -] -``` - -**Note**: We extend NIP-84 with file/line tags for code highlighting, which is reasonable for git repository use case. - -**Implementation**: `src/lib/services/nostr/highlights-service.ts:278-327` - -### ✅ NIP-98: HTTP Authentication (Kind 27235) - -**Status**: Compliant - -- Uses kind 27235 for HTTP auth events -- Content is empty (SHOULD be empty per spec) ✅ -- Includes `u` tag with absolute URL -- Includes `method` tag with HTTP method -- Includes optional `payload` tag with SHA256 hash of request body -- Validates event is within 60 seconds -- Verifies event signature -- Normalizes URLs for comparison (removes trailing slashes) - -**Example from code**: -```typescript -{ - kind: 27235, - content: '', - tags: [ - ['u', url], - ['method', method.toUpperCase()], - ...(bodyHash ? [['payload', bodyHash]] : []) - ] -} -``` - -**Implementation**: `src/lib/services/nostr/nip98-auth.ts` - -### ⚠️ Custom: Ownership Transfer (Kind 1641) - -**Status**: Custom implementation (not in any NIP) - -- Uses kind 1641 (not defined in any NIP) -- Non-replaceable event (maintains immutable chain) -- Tags: - - `a`: Repository address (`30617::`) - - `p`: New owner pubkey (or same owner for self-transfer) - - `d`: Repository identifier - - `t`: `self-transfer` marker for initial ownership proof - -**Rationale**: NIP-34 doesn't define ownership transfers. This is a custom extension for our use case. - -### ⚠️ Custom: Commit Signature (Kind 1640) - -**Status**: Custom implementation (not in any NIP) - -- Uses kind 1640 (not defined in any NIP) -- Tags: - - `author`: Author name and email - - `message`: Commit message - - `commit`: Final commit hash (added after commit is created) - - `e`: Optional reference to NIP-98 auth event - -**Rationale**: Dedicated kind to avoid spamming the feed (instead of using kind 1). - -### ⚠️ Custom: Branch Protection (Kind 30620) - -**Status**: Custom implementation (not in any NIP) - -- Uses kind 30620 (not defined in any NIP) -- Tags: - - `d`: Repository name - - `a`: Repository address - - `branch`: Branch name and protection settings - -**Rationale**: NIP-34 doesn't define branch protection. This is a custom extension. - -## Issues Found - -### 1. NIP-22 Comment Tag Format - -**Issue**: In `createCommentEvent`, we were using: -```typescript -['E', rootEventId, '', rootPubkey] -``` - -**NIP-22 Spec**: The `E` tag format should be: -```jsonc -["E", "", "", ""] -``` - -**Fix**: Updated to include relay hint in position 2: -```typescript -['E', rootEventId, relay || '', rootPubkey] -``` - -**Status**: ✅ **Fixed** - Now includes optional relay hint parameter. - -### 2. NIP-34 PR Tags - -**Issue**: We were missing the `r` tag with earliest unique commit ID in PR creation. - -**NIP-34 Spec**: PRs SHOULD include: -```jsonc -["r", ""] -``` - -**Fix**: Updated `createPullRequest` to accept optional `earliestUniqueCommit` parameter and add `r` tag if provided. - -**Status**: ✅ **Fixed** - Service method now accepts and includes `r` tag. Callers (client-side event creation) should extract from repo announcement and include it. - -### 3. NIP-34 Issue Tags - -**Issue**: We were missing the `r` tag with earliest unique commit ID in issue creation. - -**NIP-34 Spec**: Issues SHOULD include: -```jsonc -["r", ""] -``` +### Core Protocol -**Fix**: Updated `createIssue` to accept optional `earliestUniqueCommit` parameter and add `r` tag if provided. +- **[NIP-01: Basic Protocol Flow](01.md)** - Event structure, signatures, and client-relay communication + - Foundation for all Nostr events + - Used for relay write proof fallback (kind 1) -**Status**: ✅ **Fixed** - Service method now accepts and includes `r` tag. Callers (client-side event creation) should extract from repo announcement and include it. +### Authentication & Identity -### 4. NIP-34 Status Event Tags +- **[NIP-02: Contact List](02.md)** - Contact list (kind 3) + - Used for repository filtering ("Show only my repos and those of my contacts") + - Fallback for relay discovery -**Issue**: We were missing the `r` tag with earliest unique commit in status events. +- **[NIP-07: Browser Extension Authentication](07.md)** - `window.nostr` capability + - Primary authentication method for GitRepublic + - Used for signing all repository-related events -**NIP-34 Spec**: Status events SHOULD include: -```jsonc -["r", ""] -``` +- **[NIP-19: bech32-encoded Entities](19.md)** - bech32 encoding (npub, nsec, note, nevent, naddr) + - User-friendly display of pubkeys and event references + - Used throughout the UI for repository URLs and search -**Fix**: Added comment noting that callers should add `r` tag if available. The service methods don't have direct access to repo announcements, so this should be added by the caller when creating status events. +- **[NIP-98: HTTP Authentication](98.md)** - HTTP auth events (kind 27235) + - Authenticates git operations (push, pull, clone) + - Authenticates API requests -**Status**: ✅ **Documented** - Service methods note that callers should include `r` tag. This is acceptable since status events are typically created client-side with access to repo announcement. +### Event Management -## Recommendations +- **[NIP-09: Event Deletion](09.md)** - Deletion requests (kind 5) + - Used for cleaning up failed fork attempts -1. ✅ **Add `r` tags to PRs, Issues** - Service methods now accept optional `earliestUniqueCommit` parameter -2. ✅ **Fix NIP-22 comment tag format** - Updated to include relay hint in correct position -3. **Document custom event kinds** (1640, 1641, 30620) in a separate document or propose them as NIPs -4. **Consider adding NIP-34 patch support** (kind 1617) if needed for smaller changes -5. **Client-side event creation**: When creating PRs/Issues client-side, extract `r` tag with `euc` marker from repo announcement and include it in the event +- **[NIP-10: Event References](10.md)** - Event references (e, p tags) + - Used in patch series for threading patches -## Summary +- **[NIP-22: Comments](22.md)** - Comment events (kind 1111) + - Threaded discussions on PRs, issues, and patches -- ✅ **11 NIPs correctly implemented** -- ✅ **All compliance issues fixed**: NIP-22 tag format corrected, NIP-34 `r` tags added to service methods -- ✅ **3 custom event kinds** properly documented and used +### Git Collaboration -Overall compliance is **excellent**. All identified issues have been fixed. Service methods now support NIP-34 compliance, and callers should extract the earliest unique commit from repo announcements when creating PRs/Issues. +- **[NIP-34: Git Repository Announcements](34.md)** - Git collaboration on Nostr + - **30617**: Repository announcements + - **30618**: Repository state + - **1617**: Patches + - **1618**: Pull requests + - **1619**: Pull request updates + - **1621**: Issues + - **1630-1633**: Status events (Open, Applied/Merged, Closed, Draft) ---- +### Relay & Discovery -## Complete Event Kind Reference +- **[NIP-65: Relay List Metadata](65.md)** - Relay list (kind 10002) + - Discovers user's preferred relays for publishing events -This section provides complete JSON examples for all event kinds used by gitrepublic-web, including both standard NIP-defined kinds and our custom extensions. +### Content Features -### NIP-01: Basic Event Structure +- **[NIP-84: Highlights](84.md)** - Highlight events (kind 9802) + - Code selection and review features + - Extended with file/line tags for code context -All events follow this structure: - -```jsonc -{ - "id": "<32-bytes lowercase hex-encoded sha256 of the serialized event data>", - "pubkey": "<32-bytes lowercase hex-encoded public key of the event creator>", - "created_at": , - "kind": , - "tags": [ - [...], - // ... - ], - "content": , - "sig": "<64-bytes lowercase hex of the signature>" -} -``` - -### NIP-02: Contact List (Kind 3) - -Used for discovering user's preferred relays (fallback method). - -**Example**: -```jsonc -{ - "kind": 3, - "pubkey": "user_pubkey_hex...", - "created_at": 1234567890, - "content": "", - "tags": [ - ["p", "91cf9..4e5ca", "wss://alicerelay.com/", "alice"], - ["p", "14aeb..8dad4", "wss://bobrelay.com/nostr", "bob"] - ], - "id": "...", - "sig": "..." -} -``` - -### NIP-09: Event Deletion Request (Kind 5) - -Used to request deletion of previously published events. - -**Example**: -```jsonc -{ - "kind": 5, - "pubkey": "user_pubkey_hex...", - "created_at": 1234567890, - "content": "these posts were published by accident", - "tags": [ - ["e", "dcd59..464a2"], - ["e", "968c5..ad7a4"], - ["a", "30617::"], - ["k", "30617"] - ], - "id": "...", - "sig": "..." -} -``` - -### NIP-22: Comments (Kind 1111) - -Comment events for threaded discussions. Uses uppercase tags for root scope and lowercase for parent. - -**Example - Comment on PR (Root Comment)**: -```jsonc -{ - "kind": 1111, - "pubkey": "commenter_pubkey...", - "created_at": 1234567890, - "content": "This is a root comment", - "tags": [ - ["E", "pr_event_id", "wss://relay.example.com", "pr_author_pubkey"], - ["K", "1618"], - ["P", "pr_author_pubkey", "wss://relay.example.com"], - ["e", "pr_event_id", "wss://relay.example.com", "pr_author_pubkey"], - ["k", "1618"], - ["p", "pr_author_pubkey", "wss://relay.example.com"] - ], - "id": "...", - "sig": "..." -} -``` - -**Example - Reply Comment**: -```jsonc -{ - "kind": 1111, - "pubkey": "commenter_pubkey...", - "created_at": 1234567890, - "content": "This is a reply", - "tags": [ - ["E", "pr_event_id", "wss://relay.example.com", "pr_author_pubkey"], - ["K", "1618"], - ["P", "pr_author_pubkey", "wss://relay.example.com"], - ["e", "parent_comment_event_id", "wss://relay.example.com", "parent_author_pubkey"], - ["k", "1111"], - ["p", "parent_author_pubkey", "wss://relay.example.com"] - ], - "id": "...", - "sig": "..." -} -``` - -### NIP-34: Git Repository Announcements - -#### 30617 - REPO_ANNOUNCEMENT - -Repository announcement event. This is a **replaceable event** (same `d` tag = same repo). - -**Required Tags**: -- `d`: Repository name/identifier (string) - -**Optional Tags**: -- `name`: Display name for the repository (string) -- `description`: Repository description (string) -- `clone`: Clone URL (string, can appear multiple times) -- `web`: Web UI URL (string) -- `relays`: Nostr relay URL (string, can appear multiple times) -- `maintainers`: Maintainer pubkey (string, can appear multiple times) -- `image`: Repository image URL (string) -- `banner`: Repository banner image URL (string) -- `private`: Privacy flag - set to `"true"` for private repos (string) -- `t`: Topic/tag (string, e.g., `"fork"` for forks) -- `a`: Reference to another repo (string, format: `"30617:{owner}:{repo}"`) -- `r`: Resource reference (string, e.g., earliest unique commit with `"euc"` marker) -- `p`: Referenced pubkey (string, e.g., original owner for forks) - -**Example**: -```jsonc -{ - "kind": 30617, - "pubkey": "abc123...", - "created_at": 1234567890, - "content": "", - "tags": [ - ["d", "my-repo"], - ["name", "My Awesome Repository"], - ["description", "A repository for awesome things"], - ["clone", "https://git.example.com/npub1abc.../my-repo.git"], - ["clone", "https://backup.example.com/npub1abc.../my-repo.git"], - ["relays", "wss://relay1.com"], - ["relays", "wss://relay2.com"], - ["maintainers", "npub1def..."], - ["maintainers", "npub1ghi..."], - ["image", "https://example.com/repo-image.png"], - ["banner", "https://example.com/repo-banner.png"], - ["r", "earliest_commit_hash", "euc"], - ["private", "true"] - ], - "id": "...", - "sig": "..." -} -``` - -**Example - Fork Announcement**: -```jsonc -{ - "kind": 30617, - "pubkey": "fork_owner_pubkey...", - "created_at": 1234567890, - "content": "", - "tags": [ - ["d", "my-repo"], - ["name", "My Awesome Repository (fork)"], - ["description", "Fork of My Awesome Repository"], - ["clone", "https://git.example.com/npub1fork.../my-repo.git"], - ["t", "fork"], - ["a", "30617:original_owner_pubkey.../my-repo"], - ["p", "original_owner_pubkey..."], - ["r", "earliest_commit_hash", "euc"] - ], - "id": "...", - "sig": "..." -} -``` - -#### 30618 - REPO_STATE - -Optional source of truth for the state of branches and tags in a repository. - -**Example**: -```jsonc -{ - "kind": 30618, - "pubkey": "owner_pubkey...", - "created_at": 1234567890, - "content": "", - "tags": [ - ["d", "my-repo"], - ["refs/heads/main", "commit_hash_abc123..."], - ["refs/heads/develop", "commit_hash_def456..."], - ["refs/tags/v1.0.0", "commit_hash_ghi789..."], - ["HEAD", "ref: refs/heads/main"] - ], - "id": "...", - "sig": "..." -} -``` - -#### 1617 - PATCH - -Git patch event. Used for patches under 60KB. - -**Required Tags**: -- `a`: Repository identifier (string, format: `"30617:{owner}:{repo}"`) -- `r`: Earliest unique commit ID (string) - -**Optional Tags**: -- `p`: Repository owner pubkey (string) -- `t`: `"root"` for first patch, `"root-revision"` for first patch in revision -- `commit`: Current commit ID (string) -- `parent-commit`: Parent commit ID (string) -- `commit-pgp-sig`: PGP signature (string, empty for unsigned) -- `committer`: Committer info (name, email, timestamp, timezone) - -**Example**: -```jsonc -{ - "kind": 1617, - "pubkey": "author_pubkey...", - "created_at": 1234567890, - "content": "", - "tags": [ - ["a", "30617:owner_pubkey.../my-repo"], - ["r", "earliest_unique_commit_id"], - ["p", "owner_pubkey..."], - ["t", "root"], - ["commit", "current_commit_id"], - ["r", "current_commit_id"], - ["parent-commit", "parent_commit_id"] - ], - "id": "...", - "sig": "..." -} -``` - -#### 1618 - PULL_REQUEST - -Pull request event. - -**Required Tags**: -- `a`: Repository identifier (string, format: `"30617:{owner}:{repo}"`) -- `subject`: PR subject/title (string) -- `c`: Current commit ID - tip of PR branch (string) -- `clone`: Clone URL where commit can be downloaded (string, at least one) - -**Optional Tags**: -- `r`: Earliest unique commit ID (string) - **SHOULD be included** -- `p`: Repository owner pubkey (string) -- `branch-name`: Recommended branch name (string) -- `t`: PR label (string, can appear multiple times) -- `e`: Root patch event ID (string, if PR is revision of existing patch) -- `merge-base`: Most recent common ancestor with target branch (string) - -**Example**: -```jsonc -{ - "kind": 1618, - "pubkey": "author_pubkey...", - "created_at": 1234567890, - "content": "Full PR description in markdown", - "tags": [ - ["a", "30617:owner_pubkey.../my-repo"], - ["r", "earliest_unique_commit_id"], - ["p", "owner_pubkey..."], - ["subject", "Add new feature"], - ["c", "pr_tip_commit_id"], - ["clone", "https://git.example.com/npub1.../my-repo.git"], - ["branch-name", "feature/new-feature"], - ["t", "enhancement"], - ["t", "ready-for-review"] - ], - "id": "...", - "sig": "..." -} -``` - -#### 1619 - PULL_REQUEST_UPDATE - -Pull request update event. Changes the tip of a referenced PR. - -**Required Tags**: -- `a`: Repository identifier (string) -- `E`: Pull request event ID (string, NIP-22 root) -- `P`: Pull request author pubkey (string, NIP-22 root) -- `c`: Updated tip commit ID (string) -- `clone`: Clone URL (string, at least one) - -**Optional Tags**: -- `r`: Earliest unique commit ID (string) -- `p`: Repository owner pubkey (string) -- `merge-base`: Most recent common ancestor (string) - -**Example**: -```jsonc -{ - "kind": 1619, - "pubkey": "author_pubkey...", - "created_at": 1234567890, - "content": "", - "tags": [ - ["a", "30617:owner_pubkey.../my-repo"], - ["r", "earliest_unique_commit_id"], - ["p", "owner_pubkey..."], - ["E", "original_pr_event_id"], - ["P", "pr_author_pubkey"], - ["c", "updated_tip_commit_id"], - ["clone", "https://git.example.com/npub1.../my-repo.git"] - ], - "id": "...", - "sig": "..." -} -``` - -#### 1621 - ISSUE - -Issue event for bug reports, feature requests, questions. - -**Required Tags**: -- `a`: Repository identifier (string, format: `"30617:{owner}:{repo}"`) -- `subject`: Issue subject/title (string) - -**Optional Tags**: -- `r`: Earliest unique commit ID (string) - **SHOULD be included** -- `p`: Repository owner pubkey (string) -- `t`: Issue label (string, can appear multiple times) - -**Example**: -```jsonc -{ - "kind": 1621, - "pubkey": "author_pubkey...", - "created_at": 1234567890, - "content": "Full issue description in markdown", - "tags": [ - ["a", "30617:owner_pubkey.../my-repo"], - ["r", "earliest_unique_commit_id"], - ["p", "owner_pubkey..."], - ["subject", "Bug: Something is broken"], - ["t", "bug"], - ["t", "high-priority"] - ], - "id": "...", - "sig": "..." -} -``` - -#### 1630-1633 - STATUS Events - -Status events for PRs and Issues. The most recent status event (by `created_at`) from the issue/patch author or a maintainer is considered valid. - -**1630 - STATUS_OPEN**: -```jsonc -{ - "kind": 1630, - "pubkey": "owner_pubkey...", - "created_at": 1234567890, - "content": "Reopening this issue", - "tags": [ - ["e", "pr_or_issue_event_id", "", "root"], - ["p", "repository_owner"], - ["p", "root_event_author"], - ["a", "30617:owner_pubkey.../my-repo", "wss://relay.example.com"], - ["r", "earliest_unique_commit_id"] - ], - "id": "...", - "sig": "..." -} -``` - -**1631 - STATUS_APPLIED** (Merged for PRs, Resolved for Issues): -```jsonc -{ - "kind": 1631, - "pubkey": "owner_pubkey...", - "created_at": 1234567890, - "content": "Merged via commit abc123", - "tags": [ - ["e", "pr_event_id", "", "root"], - ["p", "repository_owner"], - ["p", "pr_author"], - ["a", "30617:owner_pubkey.../my-repo", "wss://relay.example.com"], - ["r", "earliest_unique_commit_id"], - ["merge-commit", "merge_commit_id"], - ["r", "merge_commit_id"] - ], - "id": "...", - "sig": "..." -} -``` - -**1632 - STATUS_CLOSED**: -```jsonc -{ - "kind": 1632, - "pubkey": "owner_pubkey...", - "created_at": 1234567890, - "content": "Closing as duplicate", - "tags": [ - ["e", "pr_or_issue_event_id", "", "root"], - ["p", "repository_owner"], - ["p", "root_event_author"], - ["a", "30617:owner_pubkey.../my-repo", "wss://relay.example.com"], - ["r", "earliest_unique_commit_id"] - ], - "id": "...", - "sig": "..." -} -``` - -**1633 - STATUS_DRAFT**: -```jsonc -{ - "kind": 1633, - "pubkey": "author_pubkey...", - "created_at": 1234567890, - "content": "", - "tags": [ - ["e", "pr_event_id", "", "root"], - ["p", "repository_owner"], - ["p", "pr_author"], - ["a", "30617:owner_pubkey.../my-repo", "wss://relay.example.com"], - ["r", "earliest_unique_commit_id"] - ], - "id": "...", - "sig": "..." -} -``` - -### NIP-65: Relay List Metadata (Kind 10002) - -Used for discovering user's inbox/outbox relays. - -**Example**: -```jsonc -{ - "kind": 10002, - "pubkey": "user_pubkey...", - "created_at": 1234567890, - "content": "", - "tags": [ - ["r", "wss://alicerelay.example.com"], - ["r", "wss://brando-relay.com"], - ["r", "wss://expensive-relay.example2.com", "write"], - ["r", "wss://nostr-relay.example.com", "read"] - ], - "id": "...", - "sig": "..." -} -``` - -### NIP-84: Highlights (Kind 9802) - -Highlight event for code selections. Content contains the highlighted text. - -**Required Tags**: -- `a` or `e`: Source event reference (string) -- `P`: Root author pubkey (string) -- `K`: Root event kind (string) - -**Optional Tags**: -- `r`: Source URL (string) -- `p`: Attribution pubkey with optional role (string, string, string) -- `file`: File path being highlighted (string) -- `line-start`: Start line number (string) -- `line-end`: End line number (string) -- `context`: Surrounding context (string) -- `comment`: Comment text for quote highlights (string) - -**Example - Code Highlight on PR**: -```jsonc -{ - "kind": 9802, - "pubkey": "reviewer_pubkey...", - "created_at": 1234567890, - "content": "const result = await fetch(url);", - "tags": [ - ["a", "1618:pr_author_pubkey.../repo-name"], - ["e", "pr_event_id"], - ["P", "pr_author_pubkey"], - ["K", "1618"], - ["file", "src/main.ts"], - ["line-start", "42"], - ["line-end", "45"], - ["context", "// Fetch data from API"], - ["p", "pr_author_pubkey", "wss://relay.example.com", "author"] - ], - "id": "...", - "sig": "..." -} -``` - -### NIP-98: HTTP Authentication (Kind 27235) - -Ephemeral event used to authorize HTTP requests. Content SHOULD be empty. - -**Required Tags**: -- `u`: Absolute request URL (string, must match exactly) -- `method`: HTTP method (string: `"GET"`, `"POST"`, etc.) - -**Optional Tags**: -- `payload`: SHA256 hash of request body (string, hex, for POST/PUT/PATCH) - -**Example**: -```jsonc -{ - "kind": 27235, - "pubkey": "user_pubkey...", - "created_at": 1682327852, - "content": "", - "tags": [ - ["u", "https://git.example.com/npub1.../repo.git/git-receive-pack"], - ["method", "POST"], - ["payload", "sha256_hash_of_request_body"] - ], - "id": "...", - "sig": "..." -} -``` - -**Usage**: Base64-encode this event and include in `Authorization: Nostr {base64_event}` header. - -### Custom Event Kinds - -These event kinds are not defined in any NIP but are used by gitrepublic-web. They may be proposed as NIPs in the future. - -#### 1640 - COMMIT_SIGNATURE - -Git commit signature event. Used to cryptographically sign git commits using Nostr keys. - -**Required Tags**: -- `commit`: Final commit hash (string) - added after commit is created -- `author`: Author name (string) -- `author`: Author email (string, appears as second `author` tag) -- `message`: Commit message (string) - -**Optional Tags**: -- `e`: NIP-98 auth event reference (string) - -**Example**: -```jsonc -{ - "kind": 1640, - "pubkey": "committer_pubkey...", - "created_at": 1234567890, - "content": "Signed commit: abc123def456...\n\nFix bug in feature", - "tags": [ - ["commit", "abc123def456..."], - ["author", "John Doe"], - ["author", "john@example.com"], - ["message", "Fix bug in feature"], - ["e", "nip98_auth_event_id"] - ], - "id": "...", - "sig": "..." -} -``` - -**Note**: The commit hash is added to the event after the commit is created. The signature is also embedded in the git commit message as a trailer: `Nostr-Signature: `. - -#### 1641 - OWNERSHIP_TRANSFER - -Repository ownership transfer event. This is a **non-replaceable event** to maintain an immutable chain of ownership. - -**Required Tags**: -- `a`: Repository identifier (string, format: `"30617:{owner}:{repo}"`) -- `p`: New owner pubkey (string, hex or npub) -- `d`: Repository identifier (string) - -**Optional Tags**: -- `t`: `"self-transfer"` marker for initial ownership proof (string) - -**Example - Regular Transfer**: -```jsonc -{ - "kind": 1641, - "pubkey": "old_owner_pubkey...", - "created_at": 1234567890, - "content": "Transferring ownership of repository my-repo to new maintainer", - "tags": [ - ["a", "30617:old_owner_pubkey.../my-repo"], - ["p", "new_owner_pubkey..."], - ["d", "my-repo"] - ], - "id": "...", - "sig": "..." -} -``` - -**Example - Self-Transfer (Initial Ownership Proof)**: -```jsonc -{ - "kind": 1641, - "pubkey": "owner_pubkey...", - "created_at": 1234567890, - "content": "Initial ownership proof for repository my-repo", - "tags": [ - ["a", "30617:owner_pubkey.../my-repo"], - ["p", "owner_pubkey..."], - ["d", "my-repo"], - ["t", "self-transfer"] - ], - "id": "...", - "sig": "..." -} -``` - -**Note**: Self-transfers (owner → owner) are used to establish initial ownership proof when a repository is first announced. All ownership transfers are non-replaceable to maintain an immutable chain. - -#### 30620 - BRANCH_PROTECTION - -Branch protection rules. This is a **replaceable event** (same `d` tag = same repo). - -**Required Tags**: -- `d`: Repository name (string) -- `a`: Repository identifier (string, format: `"30617:{owner}:{repo}"`) - -**Branch Rule Tags** (per branch): -- `branch`: Branch name (string, appears multiple times) -- `branch`, `{name}`, `require-pr`: Require pull request (no value needed) -- `branch`, `{name}`, `allow-force-push`: Allow force push (no value needed) -- `branch`, `{name}`, `require-reviewers`: Required reviewer pubkey (string, can appear multiple times) -- `branch`, `{name}`, `require-status`: Required status check name (string, can appear multiple times) -- `branch`, `{name}`, `allowed-maintainers`: Maintainer who can bypass protection (string, can appear multiple times) - -**Example**: -```jsonc -{ - "kind": 30620, - "pubkey": "owner_pubkey...", - "created_at": 1234567890, - "content": "", - "tags": [ - ["d", "my-repo"], - ["a", "30617:owner_pubkey.../my-repo"], - ["branch", "main"], - ["branch", "main", "require-pr"], - ["branch", "main", "require-reviewers", "npub1reviewer..."], - ["branch", "main", "require-reviewers", "npub2reviewer..."], - ["branch", "main", "require-status", "ci"], - ["branch", "main", "require-status", "lint"], - ["branch", "develop"], - ["branch", "develop", "require-pr"], - ["branch", "develop", "allow-force-push"], - ["branch", "develop", "allowed-maintainers", "npub1maintainer..."] - ], - "id": "...", - "sig": "..." -} -``` - -**Note**: This event is replaceable - publishing a new event with the same `d` tag replaces the previous rules. Only the repository owner can create/update branch protection rules. - -### Standard Nostr Kinds (Used for Relay Discovery) - -#### 1 - Text Note - -Used for relay write proof (fallback method when NIP-98 events are not available). - -**Example**: -```jsonc -{ - "kind": 1, - "pubkey": "user_pubkey...", - "created_at": 1234567890, - "content": "gitrepublic-write-proof", - "tags": [], - "id": "...", - "sig": "..." -} -``` - ---- - -## Summary of All Event Kinds - -| Kind | Name | NIP | Replaceable | Description | -|------|------|-----|--------------|-------------| -| 1 | Text Note | NIP-01 | No | Used for relay write proof (fallback) | -| 3 | Contact List | NIP-02 | Yes | User's follow list and relay preferences | -| 5 | Deletion Request | NIP-09 | No | Request deletion of events | -| 1111 | Comment | NIP-22 | No | Threaded comments on events | -| 1617 | Patch | NIP-34 | No | Git patch (under 60KB) | -| 1618 | Pull Request | NIP-34 | No | Pull request event | -| 1619 | Pull Request Update | NIP-34 | No | PR update (changes tip) | -| 1621 | Issue | NIP-34 | No | Issue/bug report | -| 1630 | Status Open | NIP-34 | No | Open status for PR/Issue | -| 1631 | Status Applied | NIP-34 | No | Merged/Resolved status | -| 1632 | Status Closed | NIP-34 | No | Closed status | -| 1633 | Status Draft | NIP-34 | No | Draft status | -| **1640** | **Commit Signature** | **Custom** | **No** | **Git commit signature** | -| **1641** | **Ownership Transfer** | **Custom** | **No** | **Repository ownership transfer** | -| 30617 | Repo Announcement | NIP-34 | Yes | Repository announcement | -| 30618 | Repo State | NIP-34 | Yes | Repository state (branches/tags) | -| **30620** | **Branch Protection** | **Custom** | **Yes** | **Branch protection rules** | -| 9802 | Highlight | NIP-84 | No | Code/content highlight | -| 10002 | Relay List | NIP-65 | Yes | User's inbox/outbox relays | -| 27235 | HTTP Auth | NIP-98 | No | HTTP authentication (ephemeral) | +## Custom Event Kinds -**Note**: Custom event kinds (1640, 1641, 30620) are not part of any NIP but are used by gitrepublic-web. They may be proposed as NIPs in the future. +GitRepublic uses custom event kinds not defined in any standard NIP: + +- **[Custom Event Kinds](CustomKinds.md)** + - **1640**: Commit Signature - Cryptographically sign git commits + - **1641**: Ownership Transfer - Transfer repository ownership (immutable chain) + - **30620**: Branch Protection - Enforce branch protection rules + +## Quick Reference + +### Event Kinds Used + +| Kind | Name | NIP | Replaceable | Documentation | +|------|------|-----|-------------|---------------| +| 1 | Text Note | NIP-01 | No | [01.md](01.md) | +| 3 | Contact List | NIP-02 | Yes | [02.md](02.md) | +| 5 | Deletion Request | NIP-09 | No | [09.md](09.md) | +| 1111 | Comment | NIP-22 | No | [22.md](22.md) | +| 1617 | Patch | NIP-34 | No | [34.md](34.md) | +| 1618 | Pull Request | NIP-34 | No | [34.md](34.md) | +| 1619 | Pull Request Update | NIP-34 | No | [34.md](34.md) | +| 1621 | Issue | NIP-34 | No | [34.md](34.md) | +| 1630 | Status Open | NIP-34 | No | [34.md](34.md) | +| 1631 | Status Applied | NIP-34 | No | [34.md](34.md) | +| 1632 | Status Closed | NIP-34 | No | [34.md](34.md) | +| 1633 | Status Draft | NIP-34 | No | [34.md](34.md) | +| **1640** | **Commit Signature** | **Custom** | **No** | **[CustomKinds.md](CustomKinds.md)** | +| **1641** | **Ownership Transfer** | **Custom** | **No** | **[CustomKinds.md](CustomKinds.md)** | +| 30617 | Repo Announcement | NIP-34 | Yes | [34.md](34.md) | +| 30618 | Repo State | NIP-34 | Yes | [34.md](34.md) | +| **30620** | **Branch Protection** | **Custom** | **Yes** | **[CustomKinds.md](CustomKinds.md)** | +| 9802 | Highlight | NIP-84 | No | [84.md](84.md) | +| 10002 | Relay List | NIP-65 | Yes | [65.md](65.md) | +| 27235 | HTTP Auth | NIP-98 | No | [98.md](98.md) | + +## Implementation Status + +All listed NIPs are **fully implemented** and compliant with their specifications. Each NIP document includes a "GitRepublic Usage" section describing how the NIP is used in this application. + +## Compliance Verification + +For detailed compliance verification and implementation notes, see the individual NIP documents linked above. Each document includes: +- The original NIP specification +- GitRepublic-specific usage documentation +- Implementation details and code references + +## See Also + +- [NIP-34 Documentation](34.md) - Core git collaboration features +- [Custom Event Kinds](CustomKinds.md) - GitRepublic-specific event kinds +- [Architecture FAQ](ARCHITECTURE_FAQ.md) - System architecture overview +- [Implementation Details](IMPLEMENTATION.md) - Technical implementation notes diff --git a/src/app.css b/src/app.css index dcb72bf..f4c3970 100644 --- a/src/app.css +++ b/src/app.css @@ -782,6 +782,58 @@ pre code { margin-bottom: 2rem; } +.search-section { + margin-bottom: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.search-bar-container { + width: 100%; +} + +.search-bar-container .search-input { + width: 100%; + padding: 0.75rem 1rem; + font-size: 1rem; + border: 1px solid var(--border-color); + border-radius: 0.375rem; + background: var(--card-bg); + color: var(--text-primary); + font-family: 'IBM Plex Serif', serif; + transition: all 0.2s ease; +} + +.search-bar-container .search-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(126, 40, 94, 0.1); +} + +.search-bar-container .search-input:disabled { + opacity: 0.6; + cursor: not-allowed; + background: var(--bg-secondary); +} + +.filter-checkbox { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + user-select: none; + color: var(--text-primary); + font-size: 0.9rem; +} + +.filter-checkbox input[type="checkbox"] { + cursor: pointer; + width: 1.1rem; + height: 1.1rem; + accent-color: var(--accent); +} + .repos-list { display: grid; gap: 1.5rem; diff --git a/src/lib/types/nostr.ts b/src/lib/types/nostr.ts index f5daa6b..9a68858 100644 --- a/src/lib/types/nostr.ts +++ b/src/lib/types/nostr.ts @@ -27,7 +27,7 @@ export interface NostrFilter { export const KIND = { TEXT_NOTE: 1, // NIP-01: Text note (used for relay write proof fallback) - CONTACT_LIST: 3, // NIP-02: Contact list + CONTACT_LIST: 3, // NIP-02: Contact list - See /docs for GitRepublic usage documentation DELETION_REQUEST: 5, // NIP-09: Event deletion request REPO_ANNOUNCEMENT: 30617, // NIP-34: Repository announcement REPO_STATE: 30618, // NIP-34: Repository state diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 0fd8af7..b15eafa 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -7,11 +7,17 @@ import type { NostrEvent } from '../lib/types/nostr.js'; import { nip19 } from 'nostr-tools'; import { ForkCountService } from '../lib/services/nostr/fork-count-service.js'; + import { getPublicKeyWithNIP07, isNIP07Available } from '../lib/services/nostr/nip07-signer.js'; let repos = $state([]); + let allRepos = $state([]); // Store all repos for filtering let loading = $state(true); let error = $state(null); let forkCounts = $state>(new Map()); + let searchQuery = $state(''); + let showOnlyMyContacts = $state(false); + let userPubkey = $state(null); + let contactPubkeys = $state>(new Set()); import { DEFAULT_NOSTR_RELAYS } from '../lib/config.js'; const forkCountService = new ForkCountService(DEFAULT_NOSTR_RELAYS); @@ -20,8 +26,53 @@ onMount(async () => { await loadRepos(); + await loadUserAndContacts(); }); + async function loadUserAndContacts() { + if (!isNIP07Available()) { + return; + } + + try { + userPubkey = await getPublicKeyWithNIP07(); + contactPubkeys.add(userPubkey); // Include user's own repos + + // Fetch user's kind 3 contact list + const contactEvents = await nostrClient.fetchEvents([ + { + kinds: [KIND.CONTACT_LIST], + authors: [userPubkey], + limit: 1 + } + ]); + + if (contactEvents.length > 0) { + const contactEvent = contactEvents[0]; + // Extract pubkeys from 'p' tags + for (const tag of contactEvent.tags) { + if (tag[0] === 'p' && tag[1]) { + let pubkey = tag[1]; + // Try to decode if it's an npub + try { + const decoded = nip19.decode(pubkey); + if (decoded.type === 'npub') { + pubkey = decoded.data as string; + } + } catch { + // Assume it's already a hex pubkey + } + if (pubkey) { + contactPubkeys.add(pubkey); + } + } + } + } + } catch (err) { + console.warn('Failed to load user or contacts:', err); + } + } + async function loadRepos() { loading = true; error = null; @@ -55,6 +106,7 @@ // Sort by created_at descending repos.sort((a, b) => b.created_at - a.created_at); + allRepos = [...repos]; // Store all repos for filtering // Load fork counts for all repos (in parallel, but don't block) loadForkCounts(repos).catch(err => { @@ -195,6 +247,201 @@ url?: string; ogType?: string; }; + + interface SearchResult { + repo: NostrEvent; + score: number; + matchType: string; + } + + function performSearch() { + if (!searchQuery.trim()) { + repos = [...allRepos]; + return; + } + + const query = searchQuery.trim().toLowerCase(); + const results: SearchResult[] = []; + + // Filter by contacts if enabled + let reposToSearch = allRepos; + if (showOnlyMyContacts && contactPubkeys.size > 0) { + reposToSearch = allRepos.filter(event => { + // Check if owner is in contacts + if (contactPubkeys.has(event.pubkey)) return true; + + // Check if any maintainer is in contacts + const maintainerTags = event.tags.filter(t => t[0] === 'maintainers'); + for (const tag of maintainerTags) { + for (let i = 1; i < tag.length; i++) { + let maintainerPubkey = tag[i]; + try { + const decoded = nip19.decode(maintainerPubkey); + if (decoded.type === 'npub') { + maintainerPubkey = decoded.data as string; + } + } catch { + // Assume it's already a hex pubkey + } + if (contactPubkeys.has(maintainerPubkey)) return true; + } + } + return false; + }); + } + + for (const repo of reposToSearch) { + let score = 0; + let matchType = ''; + + // Extract repo fields + const name = getRepoName(repo).toLowerCase(); + const dTag = repo.tags.find(t => t[0] === 'd')?.[1]?.toLowerCase() || ''; + const description = getRepoDescription(repo).toLowerCase(); + const cloneUrls = getCloneUrls(repo).map(url => url.toLowerCase()); + const maintainerTags = repo.tags.filter(t => t[0] === 'maintainers'); + const maintainers: string[] = []; + for (const tag of maintainerTags) { + for (let i = 1; i < tag.length; i++) { + if (tag[i]) maintainers.push(tag[i].toLowerCase()); + } + } + + // Try to decode query as hex id, naddr, or nevent + let queryHex = ''; + try { + const decoded = nip19.decode(query); + if (decoded.type === 'naddr' || decoded.type === 'nevent') { + queryHex = (decoded.data as any).id || ''; + } + } catch { + // Not a bech32 encoded value + } + + // Check if query is a hex pubkey or npub + let queryPubkey = ''; + try { + const decoded = nip19.decode(query); + if (decoded.type === 'npub') { + queryPubkey = decoded.data as string; + } + } catch { + // Check if it's a hex pubkey (64 hex chars) + if (/^[0-9a-f]{64}$/i.test(query)) { + queryPubkey = query; + } + } + + // Exact matches get highest score + if (name === query) { + score += 1000; + matchType = 'exact-name'; + } else if (dTag === query) { + score += 1000; + matchType = 'exact-d-tag'; + } else if (repo.id.toLowerCase() === query || repo.id.toLowerCase() === queryHex) { + score += 1000; + matchType = 'exact-id'; + } else if (repo.pubkey.toLowerCase() === queryPubkey.toLowerCase()) { + score += 800; + matchType = 'exact-pubkey'; + } + + // Name matches + if (name.includes(query)) { + score += name.startsWith(query) ? 100 : 50; + if (!matchType) matchType = 'name'; + } + + // D-tag matches + if (dTag.includes(query)) { + score += dTag.startsWith(query) ? 100 : 50; + if (!matchType) matchType = 'd-tag'; + } + + // Description matches + if (description.includes(query)) { + score += 30; + if (!matchType) matchType = 'description'; + } + + // Pubkey matches (owner) + if (repo.pubkey.toLowerCase().includes(query.toLowerCase()) || + (queryPubkey && repo.pubkey.toLowerCase() === queryPubkey.toLowerCase())) { + score += 200; + if (!matchType) matchType = 'pubkey'; + } + + // Maintainer matches + for (const maintainer of maintainers) { + if (maintainer.includes(query.toLowerCase())) { + score += 150; + if (!matchType) matchType = 'maintainer'; + break; + } + // Check if maintainer is npub and matches query + try { + const decoded = nip19.decode(maintainer); + if (decoded.type === 'npub') { + const maintainerPubkey = decoded.data as string; + if (maintainerPubkey.toLowerCase().includes(query.toLowerCase()) || + (queryPubkey && maintainerPubkey.toLowerCase() === queryPubkey.toLowerCase())) { + score += 150; + if (!matchType) matchType = 'maintainer'; + break; + } + } + } catch { + // Not an npub, already checked above + } + } + + // Clone URL matches + for (const url of cloneUrls) { + if (url.includes(query)) { + score += 40; + if (!matchType) matchType = 'clone-url'; + break; + } + } + + // Fulltext search in all tags and content + const allText = [ + name, + dTag, + description, + ...cloneUrls, + ...maintainers, + repo.content.toLowerCase() + ].join(' '); + + if (allText.includes(query)) { + score += 10; + if (!matchType) matchType = 'fulltext'; + } + + if (score > 0) { + results.push({ repo, score, matchType }); + } + } + + // Sort by score (descending), then by created_at (descending) + results.sort((a, b) => { + if (b.score !== a.score) { + return b.score - a.score; + } + return b.repo.created_at - a.repo.created_at; + }); + + repos = results.map(r => r.repo); + } + + // Reactive search when query or filter changes + $effect(() => { + if (!loading) { + performSearch(); + } + }); @@ -230,6 +477,29 @@ +
+
+ +
+ {#if isNIP07Available() && userPubkey} + + {/if} +
+ {#if error}
Error loading repositories: {error} diff --git a/src/routes/signup/+page.svelte b/src/routes/signup/+page.svelte index bf625e6..4c92704 100644 --- a/src/routes/signup/+page.svelte +++ b/src/routes/signup/+page.svelte @@ -270,13 +270,13 @@
@@ -298,7 +298,7 @@
Clone URLs - {$page.data.gitDomain || 'localhost:6543'} will be added automatically + {$page.data.gitDomain || 'localhost:6543'} will be added automatically, but you can add any existing ones here.
{#each cloneUrls as url, index}