From 3a139473bdf3054869fa86733880a69b4f5ce82a Mon Sep 17 00:00:00 2001 From: beardthelion <56458543+beardthelion@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:34:38 -0500 Subject: [PATCH 1/7] docs: add pull request template --- .github/pull_request_template.md | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..de9bedf --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,63 @@ + + +## Summary + + + + + +## Motivation & context + + +Closes # + +## Kind of change + +- [ ] Bug fix +- [ ] Feature +- [ ] Security fix +- [ ] Docs +- [ ] Tests / CI +- [ ] Refactor (no behavior change) +- [ ] Breaking or protocol change (issue required first) + +## What changed + + + +- + +## How a reviewer can verify + + + +```sh + +``` + +## Before you request review + +- [ ] Scope is one logical change; no unrelated churn +- [ ] `cargo test --workspace` passes locally +- [ ] New behavior is covered by tests (required for fixes) +- [ ] `cargo fmt --all` and `cargo clippy --workspace --all-targets -- -D warnings` are clean +- [ ] Commit titles use Conventional Commits (`feat(...)`, `fix(...)`, `docs(...)`) +- [ ] Docs / `.env.example` updated if behavior or config changed (or N/A) +- [ ] Checked existing PRs so this isn't a duplicate + +## Protocol & signing impact + + + +- [ ] Touches DID / `did:key`, Ed25519 / RFC 9421 signatures, UCAN, ref certs, or P2P wire formats +- [ ] Discussed in an issue before implementation +- [ ] Backward-compatible with existing nodes and previously signed history + +## Notes for reviewers + + From cb2da8b88d286c8ea4b7bb2ffb31d4bb1a696aa7 Mon Sep 17 00:00:00 2001 From: beardthelion <56458543+beardthelion@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:35:22 -0500 Subject: [PATCH 2/7] docs: add structured issue forms and template chooser config --- .github/ISSUE_TEMPLATE/bug_report.yml | 66 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 +++ .github/ISSUE_TEMPLATE/feature_request.yml | 34 +++++++++++ 3 files changed, 108 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..20c37b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,66 @@ +name: Bug report +description: Report a defect in a Gitlawb crate or node behavior +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for the report. Please fill in the fields below so we can reproduce the issue. + For security vulnerabilities, do NOT open an issue — follow SECURITY.md instead. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear description of the bug. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + - type: textarea + id: repro + attributes: + label: Steps to reproduce + placeholder: | + 1. ... + 2. ... + 3. ... + validations: + required: true + - type: dropdown + id: crate + attributes: + label: Affected crate + options: + - gitlawb-node + - gl + - git-remote-gitlawb + - gitlawb-core + - not sure / other + validations: + required: true + - type: input + id: version + attributes: + label: Version or commit + description: Output of the binary's `--version`, or the git commit you built. + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: OS, Rust version (`rustc --version`), and anything else relevant. + validations: + required: false + - type: textarea + id: logs + attributes: + label: Relevant logs + description: Paste logs or errors. This will be rendered as code, no need for backticks. + render: shell + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..6cde7be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/Gitlawb/node/blob/main/SECURITY.md + about: Please report security issues privately per our security policy, not as a public issue. + - name: Questions & discussion + url: https://github.com/Gitlawb/node/discussions + about: For usage questions and open-ended discussion. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..e676a9e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,34 @@ +name: Feature request +description: Suggest an improvement or new capability +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: Problem / use case + description: What are you trying to do that's hard or impossible today? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + validations: + required: false + - type: dropdown + id: protocol + attributes: + label: Does this touch the protocol? + description: Identity, signatures, UCAN, ref certs, or P2P wire formats. Protocol changes need an issue discussion before any PR. + options: + - "No" + - "Yes" + - "Not sure" + validations: + required: true From f7946ddf438b8971fb398aae51e8e74093057ad9 Mon Sep 17 00:00:00 2001 From: beardthelion <56458543+beardthelion@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:36:13 -0500 Subject: [PATCH 3/7] chore: add CODEOWNERS for CI, release, and security paths --- .github/CODEOWNERS | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..d331d1a --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,11 @@ +# Ownership for CI, release, and security-sensitive infrastructure. +# A PR that edits these paths requires review from a code owner once +# branch protection's "Require review from Code Owners" is enabled. + +/.github/ @beardthelion +/Dockerfile @beardthelion +/Dockerfile.bins @beardthelion +/docker-compose.yml @beardthelion +/release-please-config.json @beardthelion +/.release-please-manifest.json @beardthelion +/SECURITY.md @beardthelion From 750fa1f4cc09c1d1ef9e81624dbe4e030b11874a Mon Sep 17 00:00:00 2001 From: beardthelion <56458543+beardthelion@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:37:31 -0500 Subject: [PATCH 4/7] ci: add quality-signal PR triage workflow --- .github/workflows/pr-triage.yml | 114 ++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 .github/workflows/pr-triage.yml diff --git a/.github/workflows/pr-triage.yml b/.github/workflows/pr-triage.yml new file mode 100644 index 0000000..e51e7b2 --- /dev/null +++ b/.github/workflows/pr-triage.yml @@ -0,0 +1,114 @@ +name: PR Triage + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + issues: write + +concurrency: + group: pr-triage-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + triage: + name: Quality-signal triage + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Label and guide + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + script: | + const pr = context.payload.pull_request; + const { owner, repo } = context.repo; + const prNumber = pr.number; + + // Trusted authors bypass triage entirely. + const trusted = ["OWNER", "MEMBER", "COLLABORATOR"]; + if (trusted.includes(pr.author_association)) { + core.info(`Author association ${pr.author_association} is trusted; skipping triage.`); + return; + } + + // Labels this workflow owns. Anything here may be added or removed each run. + const MANAGED = ["needs-issue", "needs-description", "needs-tests", "workflow-change"]; + const want = new Set(); + + const body = pr.body || ""; + + // Linked issue: look for "closes/fixes/resolves #N" or any bare #N reference. + const hasLinkedIssue = + /\b(close[sd]?|fix(e[sd])?|resolve[sd]?)\b[^\n]*#\d+/i.test(body) || + /#\d+/.test(body); + if (!hasLinkedIssue) want.add("needs-issue"); + + // Description substance: strip HTML comments and markdown headings, then measure. + const stripped = body + .replace(//g, "") + .replace(/^#+.*$/gm, "") + .trim(); + if (stripped.length < 50) want.add("needs-description"); + + // File-based signals. + const files = await github.paginate(github.rest.pulls.listFiles, { + owner, repo, pull_number: prNumber, per_page: 100, + }); + const names = files.map(f => f.filename); + + const isFork = pr.head.repo && pr.head.repo.full_name !== pr.base.repo.full_name; + const touchesWorkflows = names.some(n => n.startsWith(".github/workflows/")); + if (isFork && touchesWorkflows) want.add("workflow-change"); + + const changedRust = names.some(n => n.startsWith("crates/") && n.endsWith(".rs")); + const touchedTests = names.some(n => n.includes("/tests/") || n.endsWith("_test.rs")); + if (changedRust && !touchedTests) want.add("needs-tests"); + + // Reconcile labels: add wanted managed labels, remove managed labels no longer wanted. + const current = (await github.rest.issues.listLabelsOnIssue({ + owner, repo, issue_number: prNumber, + })).data.map(l => l.name); + + const toAdd = [...want].filter(l => !current.includes(l)); + const toRemove = MANAGED.filter(l => current.includes(l) && !want.has(l)); + + if (toAdd.length) { + await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: toAdd }); + } + for (const name of toRemove) { + await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name }) + .catch(() => {}); // ignore if already gone + } + + // Guidance comment: only for advisory signals, and only once. + const advisory = ["needs-issue", "needs-description", "needs-tests"].filter(l => want.has(l)); + if (!advisory.length) return; + + const MARKER = ""; + const comments = await github.paginate(github.rest.issues.listComments, { + owner, repo, issue_number: prNumber, per_page: 100, + }); + if (comments.some(c => c.body && c.body.includes(MARKER))) { + core.info("Guidance comment already present; not reposting."); + return; + } + + const reasons = { + "needs-issue": "Link the issue this addresses (`Closes #123`). For protocol changes, open an issue first.", + "needs-description": "Add a short summary of what changes and why — the template's Summary and Motivation sections.", + "needs-tests": "This changes Rust source but no tests changed. Tests are required for fixes and strongly encouraged for features.", + }; + const lines = advisory.map(l => `- ${reasons[l]}`).join("\n"); + const comment = [ + MARKER, + "Thanks for the contribution. A couple of things will help us review this faster:", + "", + lines, + "", + "See [CONTRIBUTING.md](https://github.com/Gitlawb/node/blob/main/CONTRIBUTING.md). Update the PR and these notes clear automatically.", + ].join("\n"); + + await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: comment }); From 0bcdedbd38ddf6702adf1c844c6f8f6b2768ef08 Mon Sep 17 00:00:00 2001 From: beardthelion <56458543+beardthelion@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:38:49 -0500 Subject: [PATCH 5/7] ci: add label-gated auto-stale workflow for ignored PRs --- .github/workflows/stale.yml | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..0ecebc7 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,39 @@ +name: Stale PRs + +on: + schedule: + - cron: "30 1 * * *" # daily at 01:30 UTC + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + stale: + name: Flag and close ignored PRs + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 + with: + days-before-stale: 14 + days-before-close: 7 + # Act on PRs only; never on issues. + days-before-issue-stale: -1 + days-before-issue-close: -1 + # Only PRs the triage step flagged as needing an author response. + only-pr-labels: "needs-issue,needs-description" + stale-pr-label: "stale" + exempt-pr-labels: "workflow-change,security,pinned" + remove-stale-when-updated: true + stale-pr-message: > + This PR has been inactive for 14 days and is still missing a linked issue + or a description. It will be closed in 7 days if there's no update. Push a + change or leave a comment to keep it open — no hard feelings, you can reopen + anytime. + close-pr-message: > + Closing due to inactivity. Reopen whenever you're ready to update it — see + CONTRIBUTING.md for what helps us review quickly. + ascending: true From fabbf6c5f7a7ee76f61956e9e6401fc1159e3b76 Mon Sep 17 00:00:00 2001 From: beardthelion <56458543+beardthelion@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:39:28 -0500 Subject: [PATCH 6/7] docs: document PR quality bar and triage/stale policy in CONTRIBUTING --- CONTRIBUTING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dbe4de2..d4ff77d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,6 +55,24 @@ Smart contracts live in a separate repo: [github.com/Gitlawb/contracts](https:// 4. **Conventional commits.** Use `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`. Releases are automated by [release-please](https://github.com/googleapis/release-please) — your commit prefixes drive the next version bump. 5. **Format and lint.** Run `cargo fmt --all` and `cargo clippy --workspace --all-targets -- -D warnings` before submitting. CI will reject anything that fails these. +## What gets merged, what gets closed + +We welcome contributions from humans and agents alike. To keep review sustainable, PRs are +expected to clear a basic quality bar: + +- **Link an issue.** Bug fixes and features should reference an issue (`Closes #123`). For + protocol-level changes (identity, signatures, UCAN, ref certs, wire formats), open the + issue *before* writing code. +- **One change per PR.** Unrelated churn slows review and gets sent back. +- **Tests and a green pipeline.** New behavior needs tests; `cargo fmt`, `cargo clippy`, + and the full CI suite must pass. +- **A real description.** Say what changes and why. "Update code" is not a description. + +A triage bot labels PRs that are missing these and leaves a short note. Nothing is closed +automatically while you're engaging. A flagged PR that goes 14 days with no linked issue or +description gets a stale warning, and is closed 7 days later if still untouched. Closed PRs can +be reopened at any time once updated. + ## Development environment **Requirements:** From 2014f074e43a96c4e3211dc00f653f7d64ea8eb8 Mon Sep 17 00:00:00 2001 From: beardthelion <56458543+beardthelion@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:42:50 -0500 Subject: [PATCH 7/7] ci: recognize inline #[cfg(test)] tests and fix triage comment grammar --- .github/workflows/pr-triage.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-triage.yml b/.github/workflows/pr-triage.yml index e51e7b2..2b65853 100644 --- a/.github/workflows/pr-triage.yml +++ b/.github/workflows/pr-triage.yml @@ -64,7 +64,13 @@ jobs: if (isFork && touchesWorkflows) want.add("workflow-change"); const changedRust = names.some(n => n.startsWith("crates/") && n.endsWith(".rs")); - const touchedTests = names.some(n => n.includes("/tests/") || n.endsWith("_test.rs")); + // A test can live in a tests/ dir, a *_test.rs file, or — the common Rust + // pattern — an inline #[test] / #[cfg(test)] block added inside the source file. + const addsInlineTest = files.some(f => + f.filename.endsWith(".rs") && f.patch && + /^\+.*#\[(test\]|cfg\(test\))/m.test(f.patch)); + const touchedTests = + names.some(n => n.includes("/tests/") || n.endsWith("_test.rs")) || addsInlineTest; if (changedRust && !touchedTests) want.add("needs-tests"); // Reconcile labels: add wanted managed labels, remove managed labels no longer wanted. @@ -108,7 +114,7 @@ jobs: "", lines, "", - "See [CONTRIBUTING.md](https://github.com/Gitlawb/node/blob/main/CONTRIBUTING.md). Update the PR and these notes clear automatically.", + "See [CONTRIBUTING.md](https://github.com/Gitlawb/node/blob/main/CONTRIBUTING.md). Update the PR and these notes will clear automatically.", ].join("\n"); await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: comment });