Skip to content

Commit 197c4e1

Browse files
authored
Add contributor templates, issue forms, and PR triage automation (#58)
* docs: add pull request template * docs: add structured issue forms and template chooser config * chore: add CODEOWNERS for CI, release, and security paths * ci: add quality-signal PR triage workflow * ci: add label-gated auto-stale workflow for ignored PRs * docs: document PR quality bar and triage/stale policy in CONTRIBUTING * ci: recognize inline #[cfg(test)] tests and fix triage comment grammar
1 parent a5d2264 commit 197c4e1

8 files changed

Lines changed: 359 additions & 0 deletions

File tree

‎.github/CODEOWNERS‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Ownership for CI, release, and security-sensitive infrastructure.
2+
# A PR that edits these paths requires review from a code owner once
3+
# branch protection's "Require review from Code Owners" is enabled.
4+
5+
/.github/ @beardthelion
6+
/Dockerfile @beardthelion
7+
/Dockerfile.bins @beardthelion
8+
/docker-compose.yml @beardthelion
9+
/release-please-config.json @beardthelion
10+
/.release-please-manifest.json @beardthelion
11+
/SECURITY.md @beardthelion
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Bug report
2+
description: Report a defect in a Gitlawb crate or node behavior
3+
labels: ["bug"]
4+
body:
5+
- type: markdown
6+
attributes:
7+
value: |
8+
Thanks for the report. Please fill in the fields below so we can reproduce the issue.
9+
For security vulnerabilities, do NOT open an issue — follow SECURITY.md instead.
10+
- type: textarea
11+
id: what-happened
12+
attributes:
13+
label: What happened?
14+
description: A clear description of the bug.
15+
validations:
16+
required: true
17+
- type: textarea
18+
id: expected
19+
attributes:
20+
label: Expected behavior
21+
validations:
22+
required: true
23+
- type: textarea
24+
id: repro
25+
attributes:
26+
label: Steps to reproduce
27+
placeholder: |
28+
1. ...
29+
2. ...
30+
3. ...
31+
validations:
32+
required: true
33+
- type: dropdown
34+
id: crate
35+
attributes:
36+
label: Affected crate
37+
options:
38+
- gitlawb-node
39+
- gl
40+
- git-remote-gitlawb
41+
- gitlawb-core
42+
- not sure / other
43+
validations:
44+
required: true
45+
- type: input
46+
id: version
47+
attributes:
48+
label: Version or commit
49+
description: Output of the binary's `--version`, or the git commit you built.
50+
validations:
51+
required: true
52+
- type: textarea
53+
id: environment
54+
attributes:
55+
label: Environment
56+
description: OS, Rust version (`rustc --version`), and anything else relevant.
57+
validations:
58+
required: false
59+
- type: textarea
60+
id: logs
61+
attributes:
62+
label: Relevant logs
63+
description: Paste logs or errors. This will be rendered as code, no need for backticks.
64+
render: shell
65+
validations:
66+
required: false

‎.github/ISSUE_TEMPLATE/config.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: Security vulnerability
4+
url: https://github.com/Gitlawb/node/blob/main/SECURITY.md
5+
about: Please report security issues privately per our security policy, not as a public issue.
6+
- name: Questions & discussion
7+
url: https://github.com/Gitlawb/node/discussions
8+
about: For usage questions and open-ended discussion.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Feature request
2+
description: Suggest an improvement or new capability
3+
labels: ["enhancement"]
4+
body:
5+
- type: textarea
6+
id: problem
7+
attributes:
8+
label: Problem / use case
9+
description: What are you trying to do that's hard or impossible today?
10+
validations:
11+
required: true
12+
- type: textarea
13+
id: proposal
14+
attributes:
15+
label: Proposed solution
16+
validations:
17+
required: true
18+
- type: textarea
19+
id: alternatives
20+
attributes:
21+
label: Alternatives considered
22+
validations:
23+
required: false
24+
- type: dropdown
25+
id: protocol
26+
attributes:
27+
label: Does this touch the protocol?
28+
description: Identity, signatures, UCAN, ref certs, or P2P wire formats. Protocol changes need an issue discussion before any PR.
29+
options:
30+
- "No"
31+
- "Yes"
32+
- "Not sure"
33+
validations:
34+
required: true

‎.github/pull_request_template.md‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!--
2+
Thanks for contributing to Gitlawb. Keep PRs focused on one change.
3+
Protocol-level changes (identity, signatures, wire format) should start
4+
as an issue before code — see CONTRIBUTING.md.
5+
-->
6+
7+
## Summary
8+
9+
<!-- One or two sentences: what changes and why it matters. -->
10+
11+
12+
13+
## Motivation & context
14+
15+
<!-- What problem does this solve? Link prior discussion if any. -->
16+
Closes #
17+
18+
## Kind of change
19+
20+
- [ ] Bug fix
21+
- [ ] Feature
22+
- [ ] Security fix
23+
- [ ] Docs
24+
- [ ] Tests / CI
25+
- [ ] Refactor (no behavior change)
26+
- [ ] Breaking or protocol change (issue required first)
27+
28+
## What changed
29+
30+
<!-- Bullet the concrete changes. Note the crate(s) touched:
31+
gitlawb-core / gitlawb-node / gl / git-remote-gitlawb -->
32+
33+
-
34+
35+
## How a reviewer can verify
36+
37+
<!-- Commands to run, or repro steps for a bug plus proof it's fixed. -->
38+
39+
```sh
40+
41+
```
42+
43+
## Before you request review
44+
45+
- [ ] Scope is one logical change; no unrelated churn
46+
- [ ] `cargo test --workspace` passes locally
47+
- [ ] New behavior is covered by tests (required for fixes)
48+
- [ ] `cargo fmt --all` and `cargo clippy --workspace --all-targets -- -D warnings` are clean
49+
- [ ] Commit titles use Conventional Commits (`feat(...)`, `fix(...)`, `docs(...)`)
50+
- [ ] Docs / `.env.example` updated if behavior or config changed (or N/A)
51+
- [ ] Checked existing PRs so this isn't a duplicate
52+
53+
## Protocol & signing impact
54+
55+
<!-- Delete this block if your change doesn't touch the protocol. -->
56+
57+
- [ ] Touches DID / `did:key`, Ed25519 / RFC 9421 signatures, UCAN, ref certs, or P2P wire formats
58+
- [ ] Discussed in an issue before implementation
59+
- [ ] Backward-compatible with existing nodes and previously signed history
60+
61+
## Notes for reviewers
62+
63+
<!-- Anything out of scope, follow-ups, or known limitations. Optional. -->

‎.github/workflows/pr-triage.yml‎

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
name: PR Triage
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited, synchronize, reopened]
6+
7+
permissions:
8+
contents: read
9+
pull-requests: write
10+
issues: write
11+
12+
concurrency:
13+
group: pr-triage-${{ github.event.pull_request.number }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
triage:
18+
name: Quality-signal triage
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 10
21+
steps:
22+
- name: Label and guide
23+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
24+
with:
25+
script: |
26+
const pr = context.payload.pull_request;
27+
const { owner, repo } = context.repo;
28+
const prNumber = pr.number;
29+
30+
// Trusted authors bypass triage entirely.
31+
const trusted = ["OWNER", "MEMBER", "COLLABORATOR"];
32+
if (trusted.includes(pr.author_association)) {
33+
core.info(`Author association ${pr.author_association} is trusted; skipping triage.`);
34+
return;
35+
}
36+
37+
// Labels this workflow owns. Anything here may be added or removed each run.
38+
const MANAGED = ["needs-issue", "needs-description", "needs-tests", "workflow-change"];
39+
const want = new Set();
40+
41+
const body = pr.body || "";
42+
43+
// Linked issue: look for "closes/fixes/resolves #N" or any bare #N reference.
44+
const hasLinkedIssue =
45+
/\b(close[sd]?|fix(e[sd])?|resolve[sd]?)\b[^\n]*#\d+/i.test(body) ||
46+
/#\d+/.test(body);
47+
if (!hasLinkedIssue) want.add("needs-issue");
48+
49+
// Description substance: strip HTML comments and markdown headings, then measure.
50+
const stripped = body
51+
.replace(/<!--[\s\S]*?-->/g, "")
52+
.replace(/^#+.*$/gm, "")
53+
.trim();
54+
if (stripped.length < 50) want.add("needs-description");
55+
56+
// File-based signals.
57+
const files = await github.paginate(github.rest.pulls.listFiles, {
58+
owner, repo, pull_number: prNumber, per_page: 100,
59+
});
60+
const names = files.map(f => f.filename);
61+
62+
const isFork = pr.head.repo && pr.head.repo.full_name !== pr.base.repo.full_name;
63+
const touchesWorkflows = names.some(n => n.startsWith(".github/workflows/"));
64+
if (isFork && touchesWorkflows) want.add("workflow-change");
65+
66+
const changedRust = names.some(n => n.startsWith("crates/") && n.endsWith(".rs"));
67+
// A test can live in a tests/ dir, a *_test.rs file, or — the common Rust
68+
// pattern — an inline #[test] / #[cfg(test)] block added inside the source file.
69+
const addsInlineTest = files.some(f =>
70+
f.filename.endsWith(".rs") && f.patch &&
71+
/^\+.*#\[(test\]|cfg\(test\))/m.test(f.patch));
72+
const touchedTests =
73+
names.some(n => n.includes("/tests/") || n.endsWith("_test.rs")) || addsInlineTest;
74+
if (changedRust && !touchedTests) want.add("needs-tests");
75+
76+
// Reconcile labels: add wanted managed labels, remove managed labels no longer wanted.
77+
const current = (await github.rest.issues.listLabelsOnIssue({
78+
owner, repo, issue_number: prNumber,
79+
})).data.map(l => l.name);
80+
81+
const toAdd = [...want].filter(l => !current.includes(l));
82+
const toRemove = MANAGED.filter(l => current.includes(l) && !want.has(l));
83+
84+
if (toAdd.length) {
85+
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: toAdd });
86+
}
87+
for (const name of toRemove) {
88+
await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name })
89+
.catch(() => {}); // ignore if already gone
90+
}
91+
92+
// Guidance comment: only for advisory signals, and only once.
93+
const advisory = ["needs-issue", "needs-description", "needs-tests"].filter(l => want.has(l));
94+
if (!advisory.length) return;
95+
96+
const MARKER = "<!-- gitlawb-triage -->";
97+
const comments = await github.paginate(github.rest.issues.listComments, {
98+
owner, repo, issue_number: prNumber, per_page: 100,
99+
});
100+
if (comments.some(c => c.body && c.body.includes(MARKER))) {
101+
core.info("Guidance comment already present; not reposting.");
102+
return;
103+
}
104+
105+
const reasons = {
106+
"needs-issue": "Link the issue this addresses (`Closes #123`). For protocol changes, open an issue first.",
107+
"needs-description": "Add a short summary of what changes and why — the template's Summary and Motivation sections.",
108+
"needs-tests": "This changes Rust source but no tests changed. Tests are required for fixes and strongly encouraged for features.",
109+
};
110+
const lines = advisory.map(l => `- ${reasons[l]}`).join("\n");
111+
const comment = [
112+
MARKER,
113+
"Thanks for the contribution. A couple of things will help us review this faster:",
114+
"",
115+
lines,
116+
"",
117+
"See [CONTRIBUTING.md](https://github.com/Gitlawb/node/blob/main/CONTRIBUTING.md). Update the PR and these notes will clear automatically.",
118+
].join("\n");
119+
120+
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: comment });

‎.github/workflows/stale.yml‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Stale PRs
2+
3+
on:
4+
schedule:
5+
- cron: "30 1 * * *" # daily at 01:30 UTC
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pull-requests: write
11+
issues: write
12+
13+
jobs:
14+
stale:
15+
name: Flag and close ignored PRs
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 10
18+
steps:
19+
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
20+
with:
21+
days-before-stale: 14
22+
days-before-close: 7
23+
# Act on PRs only; never on issues.
24+
days-before-issue-stale: -1
25+
days-before-issue-close: -1
26+
# Only PRs the triage step flagged as needing an author response.
27+
only-pr-labels: "needs-issue,needs-description"
28+
stale-pr-label: "stale"
29+
exempt-pr-labels: "workflow-change,security,pinned"
30+
remove-stale-when-updated: true
31+
stale-pr-message: >
32+
This PR has been inactive for 14 days and is still missing a linked issue
33+
or a description. It will be closed in 7 days if there's no update. Push a
34+
change or leave a comment to keep it open — no hard feelings, you can reopen
35+
anytime.
36+
close-pr-message: >
37+
Closing due to inactivity. Reopen whenever you're ready to update it — see
38+
CONTRIBUTING.md for what helps us review quickly.
39+
ascending: true

‎CONTRIBUTING.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,24 @@ Smart contracts live in a separate repo: [github.com/Gitlawb/contracts](https://
5555
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.
5656
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.
5757

58+
## What gets merged, what gets closed
59+
60+
We welcome contributions from humans and agents alike. To keep review sustainable, PRs are
61+
expected to clear a basic quality bar:
62+
63+
- **Link an issue.** Bug fixes and features should reference an issue (`Closes #123`). For
64+
protocol-level changes (identity, signatures, UCAN, ref certs, wire formats), open the
65+
issue *before* writing code.
66+
- **One change per PR.** Unrelated churn slows review and gets sent back.
67+
- **Tests and a green pipeline.** New behavior needs tests; `cargo fmt`, `cargo clippy`,
68+
and the full CI suite must pass.
69+
- **A real description.** Say what changes and why. "Update code" is not a description.
70+
71+
A triage bot labels PRs that are missing these and leaves a short note. Nothing is closed
72+
automatically while you're engaging. A flagged PR that goes 14 days with no linked issue or
73+
description gets a stale warning, and is closed 7 days later if still untouched. Closed PRs can
74+
be reopened at any time once updated.
75+
5876
## Development environment
5977

6078
**Requirements:**

0 commit comments

Comments
 (0)