From f28806c42c16416860a5ad4cf911fa6bb70bd913 Mon Sep 17 00:00:00 2001 From: Benjamin Borbe Date: Tue, 2 Jun 2026 13:01:30 +0200 Subject: [PATCH] feat(licensing): bootstrap 4 rule blocks in go-licensing-guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructures the 4 MUST sections in docs/go-licensing-guide.md into canonical `### RULE` blocks. Same template as PRs #2-5, #8, #10. Rules added: - go-licensing/license-file-required (MUST, judgment) Public Go repos MUST have a root LICENSE file. Private/internal repos exempt. Detection requires file-existence check — not ast-grep. - go-licensing/readme-license-section-required (MUST, judgment) Public README must have a ## License H2 section pointing to LICENSE. Markdown structure check — not ast-grep. - go-licensing/source-file-header-required (MUST, addlicense) All non-vendor *.go files need the 3-line BSD-2 header. Enforcement delegates to 'addlicense -check' via make precommit (canonical tool; already wired into the toolchain). - go-licensing/copyright-year-discipline (MUST, judgment) No bulk year-updates for trivial changes; no future/non-numeric years. PR-scope diff inspection — partial ast-grep detection possible for the future-year case but bulk-update trigger needs diff-scope reasoning. rules/index.json: 42 -> 46 Walker drift detected by PR #13's check-index target during first precommit run — regenerated rules/index.json and re-ran precommit clean. New guard working as designed. Pre-emptive checks (lessons from #6, #8): no personal vault paths, no trading-domain terms, no internal contradictions, all cross-refs resolve. License-assistant agent exists at agents/license-assistant.md. --- docs/go-licensing-guide.md | 102 +++++++++++++++++++++++++++++++++++++ rules/index.json | 36 +++++++++++++ 2 files changed, 138 insertions(+) diff --git a/docs/go-licensing-guide.md b/docs/go-licensing-guide.md index 9422ba6..82c852c 100644 --- a/docs/go-licensing-guide.md +++ b/docs/go-licensing-guide.md @@ -28,6 +28,32 @@ Public projects in the Benjamin Borbe ecosystem use **BSD-2-Clause** (BSD-style) ## 1. LICENSE File +### RULE go-licensing/license-file-required (MUST) + +**Owner**: license-assistant +**Applies when**: a Go project published to `github.com/*` (public) does not have a `LICENSE` file in its repo root. +**Enforcement**: judgment (file-existence check; ast-grep cannot detect file absence) +**Why**: GitHub's repo metadata, package managers (`go.mod` consumers), and downstream Linux distros all key off the root `LICENSE` file. Without it, consumers can't legally redistribute, GitHub's "License" badge stays empty, and `go list -m all` license aggregators report the project as unlicensed. Private/internal repos (`bitbucket.seibert.tools` etc.) are exempt. + +#### Bad + +``` +repo/ +├── README.md +├── go.mod +└── main.go # no LICENSE file at root — public consumers can't redistribute +``` + +#### Good + +``` +repo/ +├── LICENSE # BSD-2-Clause, copyright year matches project creation +├── README.md +├── go.mod +└── main.go +``` + Place a `LICENSE` file in the root directory with the BSD-2-Clause license text: ``` @@ -67,6 +93,47 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## 2. README License Section +### RULE go-licensing/readme-license-section-required (MUST) + +**Owner**: license-assistant +**Applies when**: a public Go project's `README.md` has no `## License` (H2) section pointing at the root `LICENSE` file. +**Enforcement**: judgment (markdown section presence; ast-grep doesn't parse markdown structure) +**Why**: README is the first thing a consumer reads. Without a License section pointing at the LICENSE file, downstream users have to scroll the repo file tree to discover the license — friction that turns into "I'll just use a different library." The section is one line of work and removes a real adoption barrier. + +#### Bad + +```markdown +# my-go-tool + +CLI for processing widgets. + +## Usage + +... + +## Contributing + +... + +# (no License section — consumers must guess) +``` + +#### Good + +```markdown +# my-go-tool + +CLI for processing widgets. + +## Usage + +... + +## License + +BSD-style license. See [LICENSE](LICENSE) file for details. +``` + Add this at the end of README.md: ```markdown @@ -83,6 +150,34 @@ Keep it simple - the full text is in the LICENSE file. ## 3. Source File License Headers +### RULE go-licensing/source-file-header-required (MUST) + +**Owner**: license-assistant +**Applies when**: a public Go project has `*.go` files outside `vendor/` without the 3-line BSD-2-Clause header block at the top. +**Enforcement**: `addlicense -check` invocation via `make precommit` (the canonical tool; ast-grep can detect missing headers via first-line regex, but `addlicense` is already wired through the toolchain). +**Why**: License headers per source file are a redistribution requirement under the BSD-2-Clause terms. The 3-line header makes every file self-describing for legal review and prevents the "this file was copied into my project without provenance" failure mode that bites at audit time. `addlicense` automates the maintenance — `make precommit` should never let a public project commit a Go file without the header. + +#### Bad + +```go +// errors.go (public repo, no header) +package errors + +func Wrap(...) error { ... } +``` + +#### Good + +```go +// Copyright (c) 2026 Benjamin Borbe All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package errors + +func Wrap(...) error { ... } +``` + All `.go` files (excluding `vendor/`) must have license headers: ```go @@ -139,6 +234,13 @@ go run -mod=mod github.com/google/addlicense \ - Optionally update to a range when making substantial changes (e.g., `2020-2025`) - Not updating years is fine - it's conservative and acceptable +### RULE go-licensing/copyright-year-discipline (MUST) + +**Owner**: license-assistant +**Applies when**: a PR diff modifies copyright years in `*.go` source-file headers — either bulk-updating across many files or setting future / non-numeric years (`2099`, `present`, etc.). +**Enforcement**: judgment (diff inspection — ast-grep can detect `Copyright (c) 2099` shapes but the "bulk-update for trivial changes" trigger needs PR-scope reasoning) +**Why**: Copyright years record when copyright was *established* on a file, not when the file was last touched. Bulk year-updates obscure the original publication date (legally meaningful for derivative-work claims) and produce noisy diffs that bury the real change. Future / non-numeric years break OSS license aggregators and look unprofessional to downstream auditors. + ### What NOT to Do - Don't bulk update all years just because it's a new year - Don't update years for trivial formatting changes diff --git a/rules/index.json b/rules/index.json index 5e2d292..bab2b22 100644 --- a/rules/index.json +++ b/rules/index.json @@ -188,6 +188,42 @@ "level": "MUST", "owner": "go-http-handler-assistant" }, + { + "anchor": "go-licensing/copyright-year-discipline", + "applies_when": "a PR diff modifies copyright years in `*.go` source-file headers — either bulk-updating across many files or setting future / non-numeric years (`2099`, `present`, etc.).", + "doc_path": "docs/go-licensing-guide.md", + "enforcement": "judgment (diff inspection — ast-grep can detect `Copyright (c) 2099` shapes but the \"bulk-update for trivial changes\" trigger needs PR-scope reasoning)", + "id": "go-licensing/copyright-year-discipline", + "level": "MUST", + "owner": "license-assistant" + }, + { + "anchor": "go-licensing/license-file-required", + "applies_when": "a Go project published to `github.com/*` (public) does not have a `LICENSE` file in its repo root.", + "doc_path": "docs/go-licensing-guide.md", + "enforcement": "judgment (file-existence check; ast-grep cannot detect file absence)", + "id": "go-licensing/license-file-required", + "level": "MUST", + "owner": "license-assistant" + }, + { + "anchor": "go-licensing/readme-license-section-required", + "applies_when": "a public Go project's `README.md` has no `## License` (H2) section pointing at the root `LICENSE` file.", + "doc_path": "docs/go-licensing-guide.md", + "enforcement": "judgment (markdown section presence; ast-grep doesn't parse markdown structure)", + "id": "go-licensing/readme-license-section-required", + "level": "MUST", + "owner": "license-assistant" + }, + { + "anchor": "go-licensing/source-file-header-required", + "applies_when": "a public Go project has `*.go` files outside `vendor/` without the 3-line BSD-2-Clause header block at the top.", + "doc_path": "docs/go-licensing-guide.md", + "enforcement": "`addlicense -check` invocation via `make precommit` (the canonical tool; ast-grep can detect missing headers via first-line regex, but `addlicense` is already wired through the toolchain).", + "id": "go-licensing/source-file-header-required", + "level": "MUST", + "owner": "license-assistant" + }, { "anchor": "go-prometheus/composed-metrics-interface", "applies_when": "a single `Metrics` interface aggregates methods spanning two or more distinct functional domains (handlers + senders + schedulers + …), forcing consumers to depend on methods they don't use.",