Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
907e832
chore(deps): bump org.sonatype.central:central-publishing-maven-plugin
dependabot[bot] Aug 18, 2025
b073d36
chore(deps): bump org.apache.maven.plugins:maven-compiler-plugin
dependabot[bot] Aug 18, 2025
d71f027
chore(deps-dev): bump com.google.guava:guava
dependabot[bot] Aug 18, 2025
c7a79d2
chore(deps): bump org.projectlombok:lombok from 1.18.36 to 1.18.38
dependabot[bot] Aug 18, 2025
2e3a24e
chore(deps): bump org.bouncycastle:bcprov-jdk18on from 1.78 to 1.81
dependabot[bot] Aug 18, 2025
b3b9a5a
chore(deps): bump org.apache.maven.plugins:maven-javadoc-plugin
dependabot[bot] Aug 18, 2025
8dee1a6
chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin
dependabot[bot] Aug 18, 2025
957b407
chore(deps): bump org.apache.commons:commons-lang3 from 3.17.0 to 3.18.0
dependabot[bot] Aug 18, 2025
18f065e
chore(deps): bump org.junit.jupiter:junit-jupiter from 5.10.2 to 5.13.4
dependabot[bot] Aug 18, 2025
c0baaf7
docs: streamline README and move setup instructions
tcheeric Aug 19, 2025
a7ad2b9
Add Nostr Java API reference documentation
tcheeric Aug 19, 2025
e90f565
docs: document extending events
tcheeric Aug 19, 2025
043c8f8
docs: add custom events howto
tcheeric Aug 19, 2025
20770e4
docs: add quick API guide
tcheeric Aug 19, 2025
a93ae7b
docs: restore badges
tcheeric Aug 19, 2025
fb65751
Merge pull request #383 from tcheeric/codex/remove-badges-and-marketi…
tcheeric Aug 19, 2025
359cfc3
Merge pull request #387 from tcheeric/codex/create-nostr-java-api-usa…
tcheeric Aug 19, 2025
692a1ed
Merge pull request #386 from tcheeric/codex/add-custom-events-documen…
tcheeric Aug 19, 2025
c80737b
Merge pull request #385 from tcheeric/codex/create-documentation-for-…
tcheeric Aug 19, 2025
ffe7565
Merge pull request #384 from tcheeric/codex/create-nostr-java-api-doc…
tcheeric Aug 19, 2025
4381b09
Assign Copilot reviewer for develop PRs
tcheeric Aug 19, 2025
edce372
fix: allow PR quality gate to label issues
tcheeric Aug 19, 2025
510c233
Handle permission errors in quality gate workflow
tcheeric Aug 19, 2025
ab49f9f
fix: set PR gate label permissions
tcheeric Aug 19, 2025
dde9618
Merge pull request #388 from tcheeric/codex/create-pr-for-chat-request
tcheeric Aug 19, 2025
0b8b852
Merge pull request #382 from tcheeric/dependabot/maven/org.junit.jupi…
tcheeric Aug 19, 2025
c7697e6
Merge pull request #381 from tcheeric/dependabot/maven/org.apache.com…
tcheeric Aug 19, 2025
976d9ad
Merge pull request #380 from tcheeric/dependabot/maven/org.apache.mav…
tcheeric Aug 19, 2025
2275ba8
Merge branch 'develop' into dependabot/maven/org.apache.maven.plugins…
tcheeric Aug 19, 2025
3a96e29
Merge pull request #379 from tcheeric/dependabot/maven/org.apache.mav…
tcheeric Aug 19, 2025
4b0c563
Merge branch 'develop' into dependabot/maven/org.bouncycastle-bcprov-…
tcheeric Aug 19, 2025
dd8aa1c
Merge pull request #378 from tcheeric/dependabot/maven/org.bouncycast…
tcheeric Aug 19, 2025
b2e45e8
Merge pull request #377 from tcheeric/dependabot/maven/org.projectlom…
tcheeric Aug 19, 2025
d5ba0cb
Merge branch 'develop' into dependabot/maven/com.google.guava-guava-3…
tcheeric Aug 19, 2025
127daac
Merge pull request #376 from tcheeric/dependabot/maven/com.google.gua…
tcheeric Aug 19, 2025
e402293
Merge pull request #375 from tcheeric/dependabot/maven/org.apache.mav…
tcheeric Aug 19, 2025
4b8274f
Merge pull request #374 from tcheeric/dependabot/maven/org.sonatype.c…
tcheeric Aug 19, 2025
9e6a610
chore: shorten PR quality gate and skip dependabot
tcheeric Aug 19, 2025
296545b
Merge pull request #389 from tcheeric/codex/update-pr-quality-gate.ym…
tcheeric Aug 19, 2025
f45e2fd
docs: document PR quality gate rules
tcheeric Aug 19, 2025
36b9c16
chore: run dependabot against develop
tcheeric Aug 19, 2025
f5e009f
Merge pull request #391 from tcheeric/codex/update-dependabot.yml-for…
tcheeric Aug 19, 2025
eeebe39
docs: document naming format requirements
tcheeric Aug 19, 2025
2490c32
docs: emphasize human expertise for AI
tcheeric Aug 19, 2025
7d35891
Merge pull request #390 from tcheeric/codex/update-agents.md-with-pr-…
tcheeric Aug 19, 2025
9fabfe6
Run Copilot reviewer workflow for develop
tcheeric Aug 19, 2025
c439219
Merge pull request #392 from tcheeric/codex/fix-assign-copilot-review…
tcheeric Aug 19, 2025
2083bd9
docs: mention PR template in guidelines
tcheeric Aug 19, 2025
90d6f98
Merge branch 'develop' into codex/update-agents.md-with-pr-quality-ga…
tcheeric Aug 19, 2025
ac6336c
Merge pull request #393 from tcheeric/codex/update-agents.md-with-pr-…
tcheeric Aug 19, 2025
cc254c9
docs: list NIP URLs for Copilot
tcheeric Aug 19, 2025
01645d0
Update CONTRIBUTING.md
tcheeric Aug 19, 2025
38dbf30
Merge branch 'develop' into codex/update-agents.md-with-pr-quality-ga…
tcheeric Aug 19, 2025
1f2f3f0
Merge pull request #394 from tcheeric/codex/update-agents.md-with-pr-…
tcheeric Aug 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Repository Instructions for Copilot

- Follow the Nostr protocol (NIP-xx) specifications.
- Spec index: https://github.com/nostr-protocol/nips
- Each NIP is at `https://github.com/nostr-protocol/nips/blob/master/XX.md` (e.g. NIP-01 → https://github.com/nostr-protocol/nips/blob/master/01.md)
- All changes must include unit tests and update relevant docs.
- Use clear names and remove unused imports.
- Prefer readable, maintainable code over clever shortcuts.
- Run `mvn -q verify` locally before pushing.
20 changes: 20 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Why now?
<!-- Explain the problem, context, and why this change is needed. Link to the issue. -->
Related issue: #____

## What changed?
<!-- Brief summary; suggest where to start reviewing if many files. -->

## BREAKING
<!-- If applicable, call it out explicitly. -->
<!-- ⚠️ BREAKING: Describe migration or impact. -->

## Review focus
<!-- Ask for specific feedback, e.g., "Concurrency strategy OK?" or "API shape acceptable?" -->

## Checklist
- [ ] Scope ≤ 300 lines (or split/stack)
- [ ] Title is **verb + object** (e.g., “Refactor auth middleware to async”)
- [ ] Description links the issue and answers “why now?”
- [ ] **BREAKING** flagged if needed
- [ ] Tests/docs updated (if relevant)
24 changes: 24 additions & 0 deletions .github/workflows/assign-copilot-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Assign Copilot Reviewer

on:
pull_request:
branches: [develop]
types: [opened, reopened, ready_for_review]

permissions:
pull-requests: write

jobs:
assign:
runs-on: ubuntu-latest
steps:
- name: Request review from GitHub Copilot
uses: actions/github-script@v7
with:
script: |
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
reviewers: ['github-copilot']
});
132 changes: 132 additions & 0 deletions .github/workflows/pr-quality-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
name: PR Quality Gate — AI-Era Expertise Standard

on:
pull_request:
branches: [develop]
types: [opened, edited, synchronize, ready_for_review]

permissions:
issues: write
pull-requests: write

jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Verify Copilot instructions
run: test -s .github/copilot-instructions.md
- name: Run expertise standard checks
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const owner = context.repo.owner;
const repo = context.repo.repo;
// Fetch fresh PR data + files
const { data: prData } = await github.rest.pulls.get({
owner, repo, pull_number: pr.number
});
// Sum changed lines (additions + deletions)
const totalChanged = prData.additions + prData.deletions;
// Pull files for basic heuristics (e.g., tests touched?)
const files = await github.paginate(
github.rest.pulls.listFiles, { owner, repo, pull_number: pr.number }
);
const extsCode = ['.js','.ts','.tsx','.jsx','.py','.rb','.go','.rs','.java','.kt','.cs','.php','.c','.cc','.cpp','.m','.mm','.swift','.scala','.sh','.yml','.yaml','.json','.toml'];
const extsTests = ['.spec.','.test.','/tests/','/__tests__/'];
const codeTouched = files.some(f =>
extsCode.some(ext => f.filename.includes(ext)));
const testsTouched = files.some(f =>
extsTests.some(tok => f.filename.includes(tok)));
// 1) Scope ≤ 300 lines (from GitHub blog checklist)
const scopeOK = totalChanged <= 300;
// 2) Title = verb + object (simple verb list heuristic)
const title = prData.title.trim();
const verbs = ['Add','Fix','Update','Refactor','Remove','Implement','Document','Docs','Test','Build','Improve','Feat','Enable','Disable','Migrate'];
const titleOK = new RegExp(`^(${verbs.join('|')})\\b.+`).test(title);
// 3) Description “why now?” + links to issue
const body = (prData.body || '').trim();
const hasIssueLink = /#[0-9]+|https?:\/\/github\.com\/.+\/issues\/[0-9]+/i.test(body);
const mentionsWhy = /\bwhy\b|\bbecause\b|\brationale\b|\bcontext\b/i.test(body);
const descOK = body.length >= 50 && (mentionsWhy || hasIssueLink);
// 4) BREAKING change highlighted
const breakingFlagPresent = /\*\*?BREAKING\*\*?|⚠️\s*BREAKING|BREAKING CHANGE/i.test(title) || /\*\*?BREAKING\*\*?|⚠️\s*BREAKING|BREAKING CHANGE/i.test(body);
// Heuristic: if "breaking" appears anywhere, require emphasis flag; otherwise pass.
const containsBreakingWord = /\bbreaking\b/i.test(title) || /\bbreaking\b/i.test(body);
const breakingOK = containsBreakingWord ? breakingFlagPresent : true;
// 5) Request specific feedback
const feedbackOK = /\b(feedback|review focus|please focus|looking for|need input)\b/i.test(body);
// Soft hint: if code changed but no tests changed, nudge (not blocking per article)
const testsHint = codeTouched && !testsTouched;
// Build result table
function row(name, ok, hint='') {
const status = ok ? '✅' : '❌';
const extra = hint ? ` — ${hint}` : '';
return `| ${status} | ${name}${extra} |`;
}
const report = [
`### PR Quality Gate — AI-Era Expertise Standard`,
`This automated review checks your PR against the five items GitHub recommends for high-quality, human-in-the-loop reviews.`,
``,
`| Pass | Check |`,
`|:----:|:------|`,
row(`Scope ≤ 300 changed lines (current: ${totalChanged})`, scopeOK, scopeOK ? '' : 'Consider splitting into smaller PRs (stacking).'),
row(`Title starts with a verb + object (e.g., "Refactor auth middleware")`, titleOK),
row(`Description answers "why now?" and links an issue`, descOK, hasIssueLink ? '' : 'Add a linked issue (#123) or URL.'),
row(`Highlight breaking changes with **BREAKING** or ⚠️ BREAKING`, breakingOK, containsBreakingWord && !breakingFlagPresent ? 'Add explicit BREAKING flag.' : ''),
row(`Request specific feedback (e.g., "Concurrency strategy OK?")`, feedbackOK),
``,
testsHint ? `> ℹ️ Heads-up: Code changed but tests weren’t touched. The blog suggests reviewers read tests first—consider adding or updating tests for clarity.` : ``,
``,
`_This gate is derived from GitHub’s “Why developer expertise matters more than ever in the age of AI.”_`
].filter(Boolean).join('\n');
// Determine blocking result (fail if any required check fails)
const failures = [];
if (!scopeOK) failures.push('Scope > 300 lines');
if (!titleOK) failures.push('Title not verb + object');
if (!descOK) failures.push('Description lacks why/issue link');
if (!breakingOK) failures.push('Missing explicit BREAKING flag');
if (!feedbackOK) failures.push('No specific feedback requested');
const sameRepo = pr.head.repo.full_name === `${owner}/${repo}`;
if (sameRepo) {
try {
// Upsert a single sticky comment
const bot = (await github.rest.users.getAuthenticated()).data.login;
const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: pr.number });
const existing = comments.find(c => c.user?.login === bot && /PR Quality Gate — AI-Era/.test(c.body || ''));
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: report });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body: report });
}
// Add labels for visibility
const addLabel = async (name) => {
await github.rest.issues.addLabels({ owner, repo, issue_number: pr.number, labels: [name] });
};
const removeLabel = async (name) => {
await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name });
};
if (failures.length) {
await addLabel('needs-quality-fixes');
} else {
await removeLabel('needs-quality-fixes');
await addLabel('quality-checked');
}
} catch (error) {
if (error.message && error.message.includes('Resource not accessible by integration')) {
core.warning('Skipping comment and label updates due to insufficient permissions.');
} else {
throw error;
}
}
} else {
core.warning('PR originates from a fork; skipping comment and label updates.');
}
// Fail the job if there are blocking issues
if (failures.length) {
core.setFailed('PR failed the expertise standard: ' + failures.join(', '));
} else {
core.info('PR passes the expertise standard.');
}

46 changes: 3 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,14 @@
- Maven
- Java 21+

## Getting Started
Clone the repository and build the modules. Installing them locally allows you to add the artifacts as dependencies in your own projects:
See [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md) for installation and usage instructions.

```bash
$ git clone https://github.com/tcheeric/nostr-java.git
$ cd nostr-java
$ ./mvnw clean install
```

You can also consume published artifacts from our Maven repository instead of building from source.

### Maven

```xml
<repositories>
<repository>
<id>nostr-java</id>
<url>https://maven.398ja.xyz/releases</url>
</repository>
</repositories>

<dependency>
<groupId>xyz.tcheeric</groupId>
<artifactId>nostr-java-api</artifactId>
<version>[VERSION]</version>
</dependency>
```

Snapshot builds are available at `https://maven.398ja.xyz/snapshots`.

### Gradle

```gradle
repositories {
maven { url 'https://maven.398ja.xyz/releases' }
}

dependencies {
implementation 'xyz.tcheeric:nostr-java-api:[VERSION]'
}
```

Replace `[VERSION]` with the latest release number from the [releases page](https://github.com/tcheeric/nostr-java/releases).
For a quick API walkthrough, see [`docs/howto/use-nostr-java-api.md`](docs/howto/use-nostr-java-api.md).

See [`docs/CODEBASE_OVERVIEW.md`](docs/CODEBASE_OVERVIEW.md) for details about running tests and contributing.

## Examples
Example usages are located in the [`nostr-java-examples`](./nostr-java-examples) module. Additional demonstrations can be found in [nostr-client](https://github.com/tcheeric/nostr-client) and [SuperConductor](https://github.com/avlo/superconductor).
Examples are located in the [`nostr-java-examples`](./nostr-java-examples) module.

## Supported NIPs
The API currently implements the following [NIPs](https://github.com/nostr-protocol/nips):
Expand Down
51 changes: 51 additions & 0 deletions docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Getting Started

## Prerequisites
- Maven
- Java 21+

## Building from Source

```bash
git clone https://github.com/tcheeric/nostr-java.git
cd nostr-java
./mvnw clean install
```

## Using Maven

Artifacts are published to `https://maven.398ja.xyz/releases`:

```xml
<repositories>
<repository>
<id>nostr-java</id>
<url>https://maven.398ja.xyz/releases</url>
</repository>
</repositories>

<dependency>
<groupId>xyz.tcheeric</groupId>
<artifactId>nostr-java-api</artifactId>
<version>[VERSION]</version>
</dependency>
```

Snapshot builds are available at `https://maven.398ja.xyz/snapshots`.

## Using Gradle

```gradle
repositories {
maven { url 'https://maven.398ja.xyz/releases' }
}

dependencies {
implementation 'xyz.tcheeric:nostr-java-api:[VERSION]'
}
```

Replace `[VERSION]` with the latest release number from the [releases page](https://github.com/tcheeric/nostr-java/releases).

Examples are available in the [`nostr-java-examples`](../nostr-java-examples) module.

25 changes: 25 additions & 0 deletions docs/explanation/extending-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Extending Events

This project uses factories and registries to make it easy to introduce new event types while keeping core classes stable.

## Factory and Registry Overview

- **Event factories** (e.g. [`EventFactory`](../../nostr-java-api/src/main/java/nostr/api/factory/EventFactory.java) and its implementations) centralize event creation so that callers don't have to handle boilerplate like setting the sender, tags, or content.
- **TagRegistry** maps tag codes to concrete implementations, allowing additional tag types to be resolved at runtime without modifying `BaseTag`.

## Adding a New Event Type

1. **Define the kind.** Add a constant to [`Kind`](../../nostr-java-base/src/main/java/nostr/base/Kind.java) or reserve a custom value.
2. **Implement the event.** Create a class under `nostr.event.impl` that extends `GenericEvent` or a more specific base class.
3. **Provide a factory.** Implement a factory extending `EventFactory` to encapsulate default tags and content for the new event.
4. **Register tags.** If the event introduces new tag codes, register their factory functions with [`TagRegistry`](../../nostr-java-event/src/main/java/nostr/event/tag/TagRegistry.java).
5. **Write tests.** Add unit and integration tests covering serialization, deserialization, and NIP compliance.
6. **Follow contributing guidelines.** Run `mvn -q verify` before committing, ensure events comply with Nostr NIPs, and document your changes.

## Testing & Contribution Requirements

- Run `mvn -q verify` from the repository root and ensure all checks pass.
- Include comprehensive tests for new functionality and remove unused imports.
- Summaries of changes and test results are expected in pull requests.

Refer to the repository's `AGENTS.md` for the full list of contribution expectations.
54 changes: 54 additions & 0 deletions docs/howto/custom-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Custom Nostr Events

This guide shows how to construct and publish a Nostr event with a non-standard `kind` using **nostr-java**.

## Background

Every Nostr event must include the fields defined in [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md):

- `id`
- `pubkey`
- `created_at`
- `kind`
- `tags`
- `content`
- `sig`

Kinds that are not defined by existing NIPs may still be used. [NIP-16](https://github.com/nostr-protocol/nips/blob/master/16.md) describes how kind numbers are grouped (regular, replaceable, ephemeral and parameterized replaceable). Choose a value that does not collide with other applications.

## Example

```java
import java.util.List;

import nostr.client.springwebsocket.StandardWebSocketClient;
import nostr.event.BaseTag;
import nostr.event.impl.GenericEvent;
import nostr.event.message.EventMessage;
import nostr.id.Identity;

public class CustomEventExample {
public static void main(String[] args) throws Exception {
Identity identity = Identity.generateRandomIdentity();

int CUSTOM_KIND = 9000; // Non-standard kind
GenericEvent event = new GenericEvent(identity.getPublicKey(), CUSTOM_KIND, List.<BaseTag>of(),
"Hello from a custom kind!");

// Required fields `id` and `sig` are populated when signing
identity.sign(event);

try (StandardWebSocketClient client = new StandardWebSocketClient("wss://relay.example.com")) {
client.send(new EventMessage(event));
}
}
}
```

## Steps Explained

1. **Construct the event** – Provide the public key, custom kind, any tags, and content. The constructor fills in `created_at` automatically and initializes the list of `tags`.
2. **Sign** – `Identity.sign(event)` computes the event `id` and `sig` using the private key. Relays verify these fields against the serialized event bytes as defined in NIP‑01.
3. **Submit to a relay** – Send the event using an `EVENT` message. The example uses `StandardWebSocketClient`, but any Nostr-compatible relay transport will work.

For more information about event structure and relay communication, consult [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) and [NIP-16](https://github.com/nostr-protocol/nips/blob/master/16.md).
Loading
Loading