diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 000000000..6f650c1d9
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,100 @@
+# Repository Instructions for Copilot
+
+## NIPs
+
+- https://github.com/nostr-protocol/nips/blob/master/01.md
+- https://github.com/nostr-protocol/nips/blob/master/02.md
+- https://github.com/nostr-protocol/nips/blob/master/03.md
+- https://github.com/nostr-protocol/nips/blob/master/04.md
+- https://github.com/nostr-protocol/nips/blob/master/05.md
+- https://github.com/nostr-protocol/nips/blob/master/06.md
+- https://github.com/nostr-protocol/nips/blob/master/07.md
+- https://github.com/nostr-protocol/nips/blob/master/08.md
+- https://github.com/nostr-protocol/nips/blob/master/09.md
+- https://github.com/nostr-protocol/nips/blob/master/10.md
+- https://github.com/nostr-protocol/nips/blob/master/11.md
+- https://github.com/nostr-protocol/nips/blob/master/12.md
+- https://github.com/nostr-protocol/nips/blob/master/13.md
+- https://github.com/nostr-protocol/nips/blob/master/14.md
+- https://github.com/nostr-protocol/nips/blob/master/15.md
+- https://github.com/nostr-protocol/nips/blob/master/16.md
+- https://github.com/nostr-protocol/nips/blob/master/17.md
+- https://github.com/nostr-protocol/nips/blob/master/18.md
+- https://github.com/nostr-protocol/nips/blob/master/19.md
+- https://github.com/nostr-protocol/nips/blob/master/20.md
+- https://github.com/nostr-protocol/nips/blob/master/21.md
+- https://github.com/nostr-protocol/nips/blob/master/22.md
+- https://github.com/nostr-protocol/nips/blob/master/23.md
+- https://github.com/nostr-protocol/nips/blob/master/24.md
+- https://github.com/nostr-protocol/nips/blob/master/25.md
+- https://github.com/nostr-protocol/nips/blob/master/26.md
+- https://github.com/nostr-protocol/nips/blob/master/27.md
+- https://github.com/nostr-protocol/nips/blob/master/28.md
+- https://github.com/nostr-protocol/nips/blob/master/29.md
+- https://github.com/nostr-protocol/nips/blob/master/30.md
+- https://github.com/nostr-protocol/nips/blob/master/31.md
+- https://github.com/nostr-protocol/nips/blob/master/32.md
+- https://github.com/nostr-protocol/nips/blob/master/33.md
+- https://github.com/nostr-protocol/nips/blob/master/34.md
+- https://github.com/nostr-protocol/nips/blob/master/35.md
+- https://github.com/nostr-protocol/nips/blob/master/36.md
+- https://github.com/nostr-protocol/nips/blob/master/37.md
+- https://github.com/nostr-protocol/nips/blob/master/38.md
+- https://github.com/nostr-protocol/nips/blob/master/39.md
+- https://github.com/nostr-protocol/nips/blob/master/40.md
+- https://github.com/nostr-protocol/nips/blob/master/42.md
+- https://github.com/nostr-protocol/nips/blob/master/44.md
+- https://github.com/nostr-protocol/nips/blob/master/45.md
+- https://github.com/nostr-protocol/nips/blob/master/46.md
+- https://github.com/nostr-protocol/nips/blob/master/47.md
+- https://github.com/nostr-protocol/nips/blob/master/48.md
+- https://github.com/nostr-protocol/nips/blob/master/49.md
+- https://github.com/nostr-protocol/nips/blob/master/50.md
+- https://github.com/nostr-protocol/nips/blob/master/51.md
+- https://github.com/nostr-protocol/nips/blob/master/52.md
+- https://github.com/nostr-protocol/nips/blob/master/53.md
+- https://github.com/nostr-protocol/nips/blob/master/54.md
+- https://github.com/nostr-protocol/nips/blob/master/55.md
+- https://github.com/nostr-protocol/nips/blob/master/56.md
+- https://github.com/nostr-protocol/nips/blob/master/57.md
+- https://github.com/nostr-protocol/nips/blob/master/58.md
+- https://github.com/nostr-protocol/nips/blob/master/59.md
+- https://github.com/nostr-protocol/nips/blob/master/60.md
+- https://github.com/nostr-protocol/nips/blob/master/61.md
+- https://github.com/nostr-protocol/nips/blob/master/62.md
+- https://github.com/nostr-protocol/nips/blob/master/64.md
+- https://github.com/nostr-protocol/nips/blob/master/65.md
+- https://github.com/nostr-protocol/nips/blob/master/66.md
+- https://github.com/nostr-protocol/nips/blob/master/68.md
+- https://github.com/nostr-protocol/nips/blob/master/69.md
+- https://github.com/nostr-protocol/nips/blob/master/70.md
+- https://github.com/nostr-protocol/nips/blob/master/71.md
+- https://github.com/nostr-protocol/nips/blob/master/72.md
+- https://github.com/nostr-protocol/nips/blob/master/73.md
+- https://github.com/nostr-protocol/nips/blob/master/75.md
+- https://github.com/nostr-protocol/nips/blob/master/77.md
+- https://github.com/nostr-protocol/nips/blob/master/78.md
+- https://github.com/nostr-protocol/nips/blob/master/7D.md
+- https://github.com/nostr-protocol/nips/blob/master/84.md
+- https://github.com/nostr-protocol/nips/blob/master/86.md
+- https://github.com/nostr-protocol/nips/blob/master/87.md
+- https://github.com/nostr-protocol/nips/blob/master/88.md
+- https://github.com/nostr-protocol/nips/blob/master/89.md
+- https://github.com/nostr-protocol/nips/blob/master/90.md
+- https://github.com/nostr-protocol/nips/blob/master/92.md
+- https://github.com/nostr-protocol/nips/blob/master/94.md
+- https://github.com/nostr-protocol/nips/blob/master/96.md
+- https://github.com/nostr-protocol/nips/blob/master/98.md
+- https://github.com/nostr-protocol/nips/blob/master/99.md
+- https://github.com/nostr-protocol/nips/blob/master/A0.md
+- https://github.com/nostr-protocol/nips/blob/master/B0.md
+- https://github.com/nostr-protocol/nips/blob/master/B7.md
+- https://github.com/nostr-protocol/nips/blob/master/C0.md
+- https://github.com/nostr-protocol/nips/blob/master/C7.md
+
+## Guidelines
+
+- 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.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index d002dfda4..18687d908 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,8 +5,10 @@ updates:
schedule:
interval: "weekly"
open-pull-requests-limit: 10
+ target-branch: "develop"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
+ target-branch: "develop"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..62b0ede2f
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,20 @@
+## Why now?
+
+Related issue: #____
+
+## What changed?
+
+
+## BREAKING
+
+
+
+## Review focus
+
+
+## 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)
diff --git a/.github/workflows/assign-copilot-review.yml b/.github/workflows/assign-copilot-review.yml
new file mode 100644
index 000000000..e17276e4a
--- /dev/null
+++ b/.github/workflows/assign-copilot-review.yml
@@ -0,0 +1,24 @@
+name: Assign Copilot Reviewer
+
+on:
+ pull_request_target:
+ 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']
+ });
diff --git a/.github/workflows/pr-quality-gate.yml b/.github/workflows/pr-quality-gate.yml
new file mode 100644
index 000000000..a13329f6a
--- /dev/null
+++ b/.github/workflows/pr-quality-gate.yml
@@ -0,0 +1,138 @@
+name: PR Quality Gate
+
+on:
+ pull_request:
+ branches: [develop]
+ types: [opened, edited, synchronize, ready_for_review]
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ review:
+ if: ${{ github.actor != 'dependabot[bot]' }}
+ 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 commits = await github.paginate(
+ github.rest.pulls.listCommits, { 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 and commits follow type: description (verb + object)
+ const title = prData.title.trim();
+ const types = ['feat','fix','docs','refactor','test','chore','ci','build','perf','style'];
+ const naming = `^(${types.join('|')}):\\s+[A-Z][^\\s]*\\s+.+`;
+ const titleOK = new RegExp(naming).test(title);
+ const commitsOK = commits.every(c => new RegExp(naming).test(c.commit.message.split('\\n')[0]));
+ // 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 and commits use type: description (verb + object)`, titleOK && commitsOK),
+ 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 || !commitsOK) failures.push('Naming format invalid');
+ 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.');
+ }
+
diff --git a/AGENTS.md b/AGENTS.md
index b918f158c..3d0450a94 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,5 +1,97 @@
# Repo Guidelines
+## NIPs
+
+- https://github.com/nostr-protocol/nips/blob/master/01.md
+- https://github.com/nostr-protocol/nips/blob/master/02.md
+- https://github.com/nostr-protocol/nips/blob/master/03.md
+- https://github.com/nostr-protocol/nips/blob/master/04.md
+- https://github.com/nostr-protocol/nips/blob/master/05.md
+- https://github.com/nostr-protocol/nips/blob/master/06.md
+- https://github.com/nostr-protocol/nips/blob/master/07.md
+- https://github.com/nostr-protocol/nips/blob/master/08.md
+- https://github.com/nostr-protocol/nips/blob/master/09.md
+- https://github.com/nostr-protocol/nips/blob/master/10.md
+- https://github.com/nostr-protocol/nips/blob/master/11.md
+- https://github.com/nostr-protocol/nips/blob/master/12.md
+- https://github.com/nostr-protocol/nips/blob/master/13.md
+- https://github.com/nostr-protocol/nips/blob/master/14.md
+- https://github.com/nostr-protocol/nips/blob/master/15.md
+- https://github.com/nostr-protocol/nips/blob/master/16.md
+- https://github.com/nostr-protocol/nips/blob/master/17.md
+- https://github.com/nostr-protocol/nips/blob/master/18.md
+- https://github.com/nostr-protocol/nips/blob/master/19.md
+- https://github.com/nostr-protocol/nips/blob/master/20.md
+- https://github.com/nostr-protocol/nips/blob/master/21.md
+- https://github.com/nostr-protocol/nips/blob/master/22.md
+- https://github.com/nostr-protocol/nips/blob/master/23.md
+- https://github.com/nostr-protocol/nips/blob/master/24.md
+- https://github.com/nostr-protocol/nips/blob/master/25.md
+- https://github.com/nostr-protocol/nips/blob/master/26.md
+- https://github.com/nostr-protocol/nips/blob/master/27.md
+- https://github.com/nostr-protocol/nips/blob/master/28.md
+- https://github.com/nostr-protocol/nips/blob/master/29.md
+- https://github.com/nostr-protocol/nips/blob/master/30.md
+- https://github.com/nostr-protocol/nips/blob/master/31.md
+- https://github.com/nostr-protocol/nips/blob/master/32.md
+- https://github.com/nostr-protocol/nips/blob/master/33.md
+- https://github.com/nostr-protocol/nips/blob/master/34.md
+- https://github.com/nostr-protocol/nips/blob/master/35.md
+- https://github.com/nostr-protocol/nips/blob/master/36.md
+- https://github.com/nostr-protocol/nips/blob/master/37.md
+- https://github.com/nostr-protocol/nips/blob/master/38.md
+- https://github.com/nostr-protocol/nips/blob/master/39.md
+- https://github.com/nostr-protocol/nips/blob/master/40.md
+- https://github.com/nostr-protocol/nips/blob/master/42.md
+- https://github.com/nostr-protocol/nips/blob/master/44.md
+- https://github.com/nostr-protocol/nips/blob/master/45.md
+- https://github.com/nostr-protocol/nips/blob/master/46.md
+- https://github.com/nostr-protocol/nips/blob/master/47.md
+- https://github.com/nostr-protocol/nips/blob/master/48.md
+- https://github.com/nostr-protocol/nips/blob/master/49.md
+- https://github.com/nostr-protocol/nips/blob/master/50.md
+- https://github.com/nostr-protocol/nips/blob/master/51.md
+- https://github.com/nostr-protocol/nips/blob/master/52.md
+- https://github.com/nostr-protocol/nips/blob/master/53.md
+- https://github.com/nostr-protocol/nips/blob/master/54.md
+- https://github.com/nostr-protocol/nips/blob/master/55.md
+- https://github.com/nostr-protocol/nips/blob/master/56.md
+- https://github.com/nostr-protocol/nips/blob/master/57.md
+- https://github.com/nostr-protocol/nips/blob/master/58.md
+- https://github.com/nostr-protocol/nips/blob/master/59.md
+- https://github.com/nostr-protocol/nips/blob/master/60.md
+- https://github.com/nostr-protocol/nips/blob/master/61.md
+- https://github.com/nostr-protocol/nips/blob/master/62.md
+- https://github.com/nostr-protocol/nips/blob/master/64.md
+- https://github.com/nostr-protocol/nips/blob/master/65.md
+- https://github.com/nostr-protocol/nips/blob/master/66.md
+- https://github.com/nostr-protocol/nips/blob/master/68.md
+- https://github.com/nostr-protocol/nips/blob/master/69.md
+- https://github.com/nostr-protocol/nips/blob/master/70.md
+- https://github.com/nostr-protocol/nips/blob/master/71.md
+- https://github.com/nostr-protocol/nips/blob/master/72.md
+- https://github.com/nostr-protocol/nips/blob/master/73.md
+- https://github.com/nostr-protocol/nips/blob/master/75.md
+- https://github.com/nostr-protocol/nips/blob/master/77.md
+- https://github.com/nostr-protocol/nips/blob/master/78.md
+- https://github.com/nostr-protocol/nips/blob/master/7D.md
+- https://github.com/nostr-protocol/nips/blob/master/84.md
+- https://github.com/nostr-protocol/nips/blob/master/86.md
+- https://github.com/nostr-protocol/nips/blob/master/87.md
+- https://github.com/nostr-protocol/nips/blob/master/88.md
+- https://github.com/nostr-protocol/nips/blob/master/89.md
+- https://github.com/nostr-protocol/nips/blob/master/90.md
+- https://github.com/nostr-protocol/nips/blob/master/92.md
+- https://github.com/nostr-protocol/nips/blob/master/94.md
+- https://github.com/nostr-protocol/nips/blob/master/96.md
+- https://github.com/nostr-protocol/nips/blob/master/98.md
+- https://github.com/nostr-protocol/nips/blob/master/99.md
+- https://github.com/nostr-protocol/nips/blob/master/A0.md
+- https://github.com/nostr-protocol/nips/blob/master/B0.md
+- https://github.com/nostr-protocol/nips/blob/master/B7.md
+- https://github.com/nostr-protocol/nips/blob/master/C0.md
+- https://github.com/nostr-protocol/nips/blob/master/C7.md
+
## Description
nostr-java is a java implementation of the nostr protocol. The specification is available on github, here: https://github.com/nostr-protocol/nips
The URL format for the NIPs is https://github.com/nostr-protocol/nips/blob/master/XX.md where XX is the NIP number. For example, the specification for NIP-01 is available at the URL https://github.com/nostr-protocol/nips/blob/master/01.md etc.
@@ -23,7 +115,19 @@ The URL format for the NIPs is https://github.com/nostr-protocol/nips/blob/maste
## Pull Requests
+- Use the pull request template at `.github/pull_request_template.md` and fill out all sections.
- Summarize the changes made and describe how they were tested.
- Include any limitations or known issues in the description.
- Add a "Network Access" section summarizing blocked domains if network requests were denied.
-- Ensure all new features, modules, or dependencies are properly documented in the `README.md` file.
\ No newline at end of file
+- Ensure all new features, modules, or dependencies are properly documented in the `README.md` file.
+## PR Quality Gate
+
+- PR summaries must reference modified files with file path citations (e.g. `F:path/to/file.java†L1-L2`).
+- PR titles and commit messages must follow the `type: description` naming format.
+- Allowed types: feat, fix, docs, refactor, test, chore, ci, build, perf, style.
+- The description should be a concise verb + object phrase (e.g., `refactor: Refactor auth middleware to async`).
+- Include a Testing section listing the commands run. Prefix each command with ✅, ⚠️, or ❌ and cite relevant terminal output.
+- If network requests fail, add a Network Access section noting blocked domains.
+- When TODOs or placeholders remain, include a Notes section.
+- Review AI-generated changes with developer expertise, ensuring you understand why the code works and that it remains resilient, scalable, and secure.
+- Use `rg` for search instead of `ls -R` or `grep -R`.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..711c6e4f3
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,16 @@
+# Contributing to nostr-java
+
+nostr-java implements the Nostr protocol. A complete index of current Nostr Implementation Possibilities (NIPs) is listed in [AGENTS.md](AGENTS.md).
+
+## Development Guidelines
+
+- Run `mvn -q verify` from the repository root before committing.
+- Use `rg` for code searches instead of `ls -R` or `grep -R`.
+- PR titles and commit messages must follow the `type: description` format.
+ - Allowed types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci`, `build`, `perf`, `style`.
+ - The description must be a concise verb + object phrase (e.g., `refactor: update auth middleware to async`).
+- Summaries in pull requests must cite file paths and include testing output.
+- Open pull requests using the template at `.github/pull_request_template.md` and complete every section.
+
+By following these conventions, contributors help keep the codebase maintainable and aligned with the Nostr specifications.
+
diff --git a/README.md b/README.md
index afc1de39f..ff133f683 100644
--- a/README.md
+++ b/README.md
@@ -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
-
-
- nostr-java
- https://maven.398ja.xyz/releases
-
-
-
-
- xyz.tcheeric
- nostr-java-api
- [VERSION]
-
-```
-
-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):
diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md
new file mode 100644
index 000000000..1d85c523d
--- /dev/null
+++ b/docs/GETTING_STARTED.md
@@ -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
+
+
+ nostr-java
+ https://maven.398ja.xyz/releases
+
+
+
+
+ xyz.tcheeric
+ nostr-java-api
+ [VERSION]
+
+```
+
+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.
+
diff --git a/docs/explanation/extending-events.md b/docs/explanation/extending-events.md
new file mode 100644
index 000000000..89891a2f1
--- /dev/null
+++ b/docs/explanation/extending-events.md
@@ -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.
diff --git a/docs/howto/custom-events.md b/docs/howto/custom-events.md
new file mode 100644
index 000000000..3dcac4b67
--- /dev/null
+++ b/docs/howto/custom-events.md
@@ -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.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).
diff --git a/docs/howto/use-nostr-java-api.md b/docs/howto/use-nostr-java-api.md
new file mode 100644
index 000000000..d08be83aa
--- /dev/null
+++ b/docs/howto/use-nostr-java-api.md
@@ -0,0 +1,44 @@
+# Using the nostr-java API
+
+This guide shows how to set up the library and publish a basic [Nostr](https://github.com/nostr-protocol/nips) event.
+
+## Minimal setup
+
+Add the API module to your project:
+
+```xml
+
+ xyz.tcheeric
+ nostr-java-api
+ [VERSION]
+
+```
+
+Replace `[VERSION]` with the latest release number.
+
+## Create, sign, and publish an event
+
+```java
+import nostr.api.NIP01;
+import nostr.id.Identity;
+
+import java.util.Map;
+
+public class QuickStart {
+ public static void main(String[] args) {
+ Identity identity = Identity.generateRandomIdentity();
+ Map relays = Map.of("local", "wss://nostr.example");
+
+ new NIP01(identity)
+ .createTextNoteEvent("Hello nostr")
+ .sign()
+ .send(relays);
+ }
+}
+```
+
+### Reference
+- [`Identity.generateRandomIdentity`](../../nostr-java-id/src/main/java/nostr/id/Identity.java)
+- [`NIP01.createTextNoteEvent`](../../nostr-java-api/src/main/java/nostr/api/NIP01.java)
+- [`EventNostr.sign`](../../nostr-java-api/src/main/java/nostr/api/EventNostr.java)
+- [`EventNostr.send`](../../nostr-java-api/src/main/java/nostr/api/EventNostr.java)
diff --git a/docs/reference/nostr-java-api.md b/docs/reference/nostr-java-api.md
new file mode 100644
index 000000000..15a0dba3e
--- /dev/null
+++ b/docs/reference/nostr-java-api.md
@@ -0,0 +1,184 @@
+# Nostr Java API Reference
+
+This document provides an overview of the public API exposed by the `nostr-java` modules. It lists the major classes, configuration objects and their key method signatures, and shows brief examples of how to use them. Where applicable, links to related [Nostr Improvement Proposals (NIPs)](https://github.com/nostr-protocol/nips) are provided.
+
+## Identity (`nostr-java-id`)
+
+### `Identity`
+Represents a Nostr identity backed by a private key. It can derive a public key and sign `ISignable` objects.
+
+```java
+public static Identity create(PrivateKey privateKey)
+public static Identity create(String privateKey)
+public static Identity generateRandomIdentity()
+public PublicKey getPublicKey()
+public Signature sign(ISignable signable)
+```
+
+**Usage:**
+```java
+Identity id = Identity.generateRandomIdentity();
+PublicKey pub = id.getPublicKey();
+Signature sig = id.sign(event);
+```
+
+## Event Model (`nostr-java-event`)
+
+### Core Types
+- `BaseMessage` – base class for all relay messages.
+- `BaseEvent` – root class for Nostr events.
+- `BaseTag` – helper for tag encoding and decoding.
+
+### Predefined Events
+The `nostr.event` package provides event implementations for many NIPs:
+
+| Class | NIP |
+|-------|-----|
+| `NIP01Event` | [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) – standard text notes. |
+| `NIP04Event` | [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) – encrypted direct messages. |
+| `NIP05Event` | [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md) – DNS identifiers. |
+| `NIP09Event` | [NIP-09](https://github.com/nostr-protocol/nips/blob/master/09.md) – event deletion. |
+| `NIP25Event` | [NIP-25](https://github.com/nostr-protocol/nips/blob/master/25.md) – reactions. |
+| `NIP52Event` | [NIP-52](https://github.com/nostr-protocol/nips/blob/master/52.md) – calendar events. |
+| `NIP99Event` | [NIP-99](https://github.com/nostr-protocol/nips/blob/master/99.md) – classified listings. |
+
+### Filters
+`Filters` and related `Filterable` implementations help build subscription requests.
+
+```java
+new Filters(Filterable... filterables)
+List getFilterByType(String type)
+void setLimit(Integer limit)
+```
+
+**Usage:**
+```java
+Filters filters = new Filters(new AuthorFilter(pubKey));
+filters.setLimit(100);
+```
+
+## WebSocket Clients (`nostr-java-client`, `nostr-java-api`)
+
+### `WebSocketClientIF`
+Abstraction over a WebSocket connection to a relay.
+
+```java
+ List send(T eventMessage) throws IOException
+List send(String json) throws IOException
+void close() throws IOException
+```
+
+### `StandardWebSocketClient`
+Spring `TextWebSocketHandler` based implementation of `WebSocketClientIF`.
+
+```java
+public StandardWebSocketClient(String relayUri)
+public List send(T eventMessage) throws IOException
+public List send(String json) throws IOException
+public void close() throws IOException
+```
+
+### `SpringWebSocketClient`
+Wrapper that adds retry logic around a `WebSocketClientIF`.
+
+```java
+public List send(BaseMessage eventMessage) throws IOException
+public List send(String json) throws IOException
+public List recover(IOException ex, String json) throws IOException
+public void close() throws IOException
+```
+
+### `NostrSpringWebSocketClient`
+High level client coordinating multiple relay connections and signing.
+
+```java
+public NostrIF setRelays(Map relays)
+public List sendEvent(IEvent event)
+public List sendRequest(List filters, String subscriptionId)
+public NostrIF sign(Identity identity, ISignable signable)
+public boolean verify(GenericEvent event)
+public Map getRelays()
+public void close()
+```
+
+### Configuration
+- `RetryConfig` – enables Spring Retry support.
+- `RelaysProperties` – maps relay names to URLs via configuration properties.
+- `RelayConfig` – loads `relays.properties` and exposes a `Map` bean.
+
+## Encryption and Cryptography
+
+### `MessageCipher`
+Strategy interface for message encryption.
+
+```java
+String encrypt(String message)
+String decrypt(String message)
+```
+
+Implementations:
+- `MessageCipher04` – NIP-04 direct message encryption.
+- `MessageCipher44` – NIP-44 payload encryption.
+
+### `Schnorr`
+Utility for Schnorr signatures (BIP-340).
+
+```java
+static byte[] sign(byte[] msg, byte[] secKey, byte[] auxRand)
+static boolean verify(byte[] msg, byte[] pubKey, byte[] sig)
+static byte[] generatePrivateKey()
+static byte[] genPubKey(byte[] secKey)
+```
+
+### `Bech32`
+Utility for Bech32/Bech32m encoding used by [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md).
+
+```java
+static String toBech32(Bech32Prefix hrp, byte[] hexKey)
+static String fromBech32(String str)
+```
+
+## Utilities (`nostr-java-util`)
+
+### `NostrUtil`
+General helper functions.
+
+```java
+static String bytesToHex(byte[] bytes)
+static byte[] hexToBytes(String hex)
+static byte[] sha256(byte[] data)
+static byte[] createRandomByteArray(int len)
+```
+
+### `NostrException`
+Base checked exception for utility methods.
+
+## Examples
+
+### Send a Text Note (NIP-01)
+```java
+Identity id = Identity.generateRandomIdentity();
+NIP01 nip01 = new NIP01(id).createTextNoteEvent("Hello Nostr");
+NostrIF client = NostrSpringWebSocketClient.getInstance(id)
+ .setRelays(Map.of("relay","wss://relay.example"));
+client.sendEvent(nip01.getEvent());
+```
+
+### Encrypted Direct Message (NIP-04)
+```java
+Identity alice = Identity.generateRandomIdentity();
+Identity bob = Identity.generateRandomIdentity();
+NIP04 dm = new NIP04(alice, bob.getPublicKey())
+ .createDirectMessageEvent("secret");
+String plaintext = NIP04.decrypt(bob, dm.getEvent());
+```
+
+### Subscription with Filters
+```java
+Filters filters = new Filters(new AuthorFilter(pubKey));
+NostrIF client = NostrSpringWebSocketClient.getInstance(id);
+List events = client.sendRequest(filters, "sub-id");
+```
+
+---
+This reference is a starting point; consult the source for complete details and additional NIP helpers.
diff --git a/pom.xml b/pom.xml
index 86d90fe7c..29a5eabe7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,24 +85,24 @@
1.81
4.3.0
- 1.78
- 3.17.0
+ 1.81
+ 3.18.0
2.19.2
- 1.18.36
+ 1.18.38
- 5.10.2
- 33.4.0-jre
+ 33.4.8-jre
+ 5.13.4
1.21.3
- 0.6.0
+ 0.8.0
0.8.13
1.7.2
- 3.13.0
+ 3.14.0
3.5.3
1.5
- 2.9.1
+ 3.11.3
3.3.1
3.5.3