diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3ab4308..29539b7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: A rule reported the wrong thing, or ago failed to run +about: A rule reported the wrong thing, or goago failed to run title: '' labels: bug assignees: '' @@ -11,7 +11,7 @@ assignees: '' ``` -$ ago ./... +$ goago ./... ``` **What you expected** @@ -27,12 +27,12 @@ package p **Which rule** - **Environment** -- `ago -version`: +- `goago -version`: - `go version`: - OS and architecture: -- `.ago.yml` (if any): +- `.goago.yml` (if any): diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 96187ce..e901cb4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - name: Question or discussion - url: https://github.com/agentstation/ago/discussions + url: https://github.com/agentstation/goago/discussions about: Ask about usage, configuration, or whether a rule fits your codebase. - name: Security vulnerability - url: https://github.com/agentstation/ago/security/policy + url: https://github.com/agentstation/goago/security/policy about: Report privately by email. Do not open a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index d8eb6b5..b7cede7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -10,11 +10,11 @@ assignees: '' **The problem** - + **What you would like** **Alternatives you considered** + own analysis.Analyzer built on goago.Rules() could already do? --> diff --git a/.github/ISSUE_TEMPLATE/rule_proposal.md b/.github/ISSUE_TEMPLATE/rule_proposal.md index 8fd962c..464b223 100644 --- a/.github/ISSUE_TEMPLATE/rule_proposal.md +++ b/.github/ISSUE_TEMPLATE/rule_proposal.md @@ -6,7 +6,7 @@ labels: rule proposal assignees: '' --- - **The construct** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc321c2..4416615 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,7 @@ jobs: - name: Rule reports a generic method run: | set -euo pipefail - go build -o "$RUNNER_TEMP/ago" ./cmd/ago + go build -o "$RUNNER_TEMP/goago" ./cmd/goago mkdir -p "$RUNNER_TEMP/gm" cd "$RUNNER_TEMP/gm" printf 'module gm\n\ngo 1.27\n' > go.mod @@ -106,7 +106,7 @@ jobs: func Plain[R any](b Box, f func(any) R) R { return f(b.v) } GO set +e - out=$("$RUNNER_TEMP/ago" -no-config -rules no-generic-methods ./...) + out=$("$RUNNER_TEMP/goago" -no-config -rules no-generic-methods ./...) status=$? set -e echo "$out" @@ -128,13 +128,13 @@ jobs: with: go-version: stable - - name: Run ago against its own source + - name: Run goago against its own source run: | - go build -o ago ./cmd/ago - ./ago -format github ./... + go build -o goago ./cmd/goago + ./goago -format github ./... - name: Report stale suppressions - run: ./ago -stale-ignores ./... + run: ./goago -stale-ignores ./... build: name: Cross-compile @@ -154,7 +154,7 @@ jobs: for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64; do echo "building $target" GOOS=${target%/*} GOARCH=${target#*/} CGO_ENABLED=0 \ - go build -o /dev/null ./cmd/ago + go build -o /dev/null ./cmd/goago done govulncheck: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c75bacd..7cfdbfb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,8 +29,8 @@ jobs: - name: Dogfood run: | - go build -o ago ./cmd/ago - ./ago ./... + go build -o goago ./cmd/goago + ./goago ./... release: name: Release diff --git a/.gitignore b/.gitignore index ce74a01..e437c95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Binaries -/ago +/goago /dist/ /build/ @@ -7,7 +7,7 @@ *.test *.out coverage.txt -ago.sarif +goago.sarif # Generated Go fuzz corpus. Seed inputs live in f.Add calls. testdata/fuzz/ diff --git a/.ago.yml b/.goago.yml similarity index 84% rename from .ago.yml rename to .goago.yml index df8fe3b..5e0a3e4 100644 --- a/.ago.yml +++ b/.goago.yml @@ -1,5 +1,5 @@ -# ago rule policy. See https://github.com/agentstation/ago#rules -# Run "ago -list" for the rule set this binary supports. +# goago rule policy. See https://github.com/agentstation/goago#rules +# Run "goago -list" for the rule set this binary supports. enable: - no-self-referential-constraints # A generic type may not name itself in its own type parameter list. @@ -20,7 +20,7 @@ enable: # no-generic-decls # No type parameters on any func or type declaration. - - no-invalid-ignore # Every //ago:ignore must name a known rule and give a reason. + - no-invalid-ignore # Every //goago:ignore must name a known rule and give a reason. - no-dot-import # Import . "pkg" is forbidden. diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 0ad7b33..303c604 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,4 +1,5 @@ version: 2 +project_name: goago before: hooks: @@ -6,9 +7,9 @@ before: - go mod tidy -diff builds: - - id: ago - binary: ago - main: ./cmd/ago + - id: goago + binary: goago + main: ./cmd/goago env: - CGO_ENABLED=0 mod_timestamp: "{{ .CommitTimestamp }}" @@ -16,7 +17,7 @@ builds: - -trimpath ldflags: - -s -w - - -X github.com/agentstation/ago.Version={{ .Version }} + - -X github.com/agentstation/goago.Version={{ .Version }} goos: - linux - darwin @@ -26,9 +27,9 @@ builds: - arm64 archives: - - id: ago + - id: goago ids: - - ago + - goago formats: [tar.gz] format_overrides: - goos: windows @@ -42,15 +43,15 @@ archives: files: - README.md - CHANGELOG.md - - ago.schema.json + - goago.schema.json - COPYRIGHT - LICENSE-MIT - LICENSE-APACHE homebrew_casks: - - name: ago + - name: goago ids: - - ago + - goago repository: owner: agentstation name: homebrew-tap @@ -64,15 +65,15 @@ homebrew_casks: commit_author: name: "GitHub Actions" email: "actions@github.com" - commit_msg_template: "Update ago to {{ .Tag }}" - homepage: https://github.com/agentstation/ago + commit_msg_template: "Update goago to {{ .Tag }}" + homepage: https://github.com/agentstation/goago description: Linter that enforces one way to write Go binaries: - - ago + - goago custom_block: | postflight_steps do on_macos do - run "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "{{ "{{staged_path}}" }}/ago"], sudo: false + run "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "{{ "{{staged_path}}" }}/goago"], sudo: false end end @@ -125,15 +126,15 @@ changelog: release: github: owner: agentstation - name: ago - name_template: "ago {{ .Tag }}" + name: goago + name_template: "goago {{ .Tag }}" header: | - `ago` enforces one way to write Go across a codebase. Developers, coding + `goago` enforces one way to write Go across a codebase. Developers, coding agents, and CI use the same configurable Go subset. ```sh - go get -tool github.com/agentstation/ago/cmd/ago@latest - go tool ago ./... + go get -tool github.com/agentstation/goago/cmd/goago@latest + go tool goago ./... ``` A config file is optional. The pinned version supplies the default policy. @@ -144,6 +145,6 @@ release: Verify downloaded archives against `checksums.txt`. Each archive ships an SBOM alongside it. footer: | - **Full changelog:** https://github.com/agentstation/ago/compare/{{ .PreviousTag }}...{{ .Tag }} + **Full changelog:** https://github.com/agentstation/goago/compare/{{ .PreviousTag }}...{{ .Tag }} prerelease: auto make_latest: '{{ if .Prerelease }}false{{ else }}true{{ end }}' diff --git a/AGENTS.md b/AGENTS.md index e6c5edb..b75c609 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ -# ago agent instructions +# goago agent instructions -`ago` is a restriction-only Go linter. Every rule rejects a legal Go construct. +`goago` is a restriction-only Go linter. Every rule rejects a legal Go construct. No rule may add syntax, rewrite code, or change semantics. ## Technical writing @@ -24,12 +24,12 @@ Use `GLOSSARY.md` for developer-facing prose. Run the installed - `rule.go` is the registry. `rule_.go` holds the analyzers. - Build every analyzer with `newAnalyzer`. Report through `checkPass.reportf`. Never use `pass.Report`. `reportf` is what applies suppression. -- `check.go` is the driver. `config.go` is the `.ago.yml` schema. +- `check.go` is the driver. `config.go` is the `.goago.yml` schema. `ignore.go` is the suppression index. `report.go` and `sarif.go` are the output formats. - Analyzers run concurrently. Cross-analyzer state needs `sync/atomic`. - Rule metadata is public interface. `Name` is the doc anchor and the config - key. `ago -explain` prints the `Rationale` string verbatim. + key. `goago -explain` prints the `Rationale` string verbatim. ## Evidence diff --git a/CHANGELOG.md b/CHANGELOG.md index 9980a1b..bacc735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,18 @@ passed. ## [Unreleased] +## [0.3.0] - 2026-09-11 + +### Changed + +- Rename the repository, Go module, package, command, and golangci-lint plugin + to `goago`, pronounced "go ago". +- Use `.goago.yml`, `.goago.yaml`, `goago.schema.json`, and `//goago:ignore` + for new policies and suppressions. Existing names still work during migration. +- Reject competing policy files in the same directory instead of selecting one silently. +- Rename release archives, the Homebrew cask, and the Agent Skill to `goago`. +- Document migration in [docs/migration.md](docs/migration.md). + ## [0.2.0] - 2026-08-30 ### Added @@ -116,7 +128,8 @@ Relative to the unreleased single-file prototype: unreported. It now uses `types.Info`. - Passing the same package twice produced duplicate findings. -[Unreleased]: https://github.com/agentstation/ago/compare/v0.2.0...HEAD -[0.2.0]: https://github.com/agentstation/ago/releases/tag/v0.2.0 -[0.1.1]: https://github.com/agentstation/ago/releases/tag/v0.1.1 -[0.1.0]: https://github.com/agentstation/ago/releases/tag/v0.1.0 +[Unreleased]: https://github.com/agentstation/goago/compare/v0.3.0...HEAD +[0.3.0]: https://github.com/agentstation/goago/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/agentstation/goago/releases/tag/v0.2.0 +[0.1.1]: https://github.com/agentstation/goago/releases/tag/v0.1.1 +[0.1.0]: https://github.com/agentstation/goago/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc163a3..9a33866 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to ago +# Contributing to goago Thanks for your interest. This document covers how to build, test, and propose changes. New rules have a higher bar than ordinary code. @@ -6,7 +6,7 @@ changes. New rules have a higher bar than ordinary code. ## Build and test ```sh -make build # build ./cmd/ago +make build # build ./cmd/goago make test # go test -race ./... make lint # gofmt, go vet, golangci-lint make govulncheck # scan the module graph @@ -25,14 +25,14 @@ skill. Set `TECHNICAL_WRITING` if the helper is not at ## Proposing a rule -`ago` is restriction-only. A rule must forbid a construct that is legal Go. +`goago` is restriction-only. A rule must forbid a construct that is legal Go. It must never require adding syntax to work around it. A proposal needs four things. **A rationale in terms of the reader.** Say what the construct costs someone reading the code cold. A second place to look. A scroll upward. An identifier whose origin is not local. "It is confusing" is not a rationale. The rationale -ships in the binary, so `ago -explain ` prints it verbatim. Write it for +ships in the binary, so `goago -explain ` prints it verbatim. Write it for somebody who just hit the finding and wants to know whether to care. **An honest default.** A rule is on by default only when the construct has a diff --git a/COPYRIGHT b/COPYRIGHT index 109e57b..a805069 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,4 +1,4 @@ -ago is dual-licensed under either of +goago is dual-licensed under either of * Apache License, Version 2.0 (see LICENSE-APACHE, or http://www.apache.org/licenses/LICENSE-2.0) diff --git a/GLOSSARY.md b/GLOSSARY.md index 35a14cc..179f076 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -5,13 +5,13 @@ when they match a row. | Term | Definition | Avoid | Status | Evidence | |---|---|---|---|---| -| ago | The restriction-only Go linter this repository builds | | approved | README.md | +| goago | The restriction-only Go linter this repository builds | | approved | README.md | | analyzer ident | The hyphen-free `Analyzer.Name` that `go/analysis` requires | | approved | rule.go | -| coding agent | A program that drives `ago` through its command, JSON catalogue, or exit status | | approved | README.md | +| coding agent | A program that drives `goago` through its command, JSON catalogue, or exit status | | approved | README.md | | F-bounded | A type parameter that appears inside its own constraint | | approved | README.md, docs/stdlib-survey.md | | finding | One reported violation of a rule | | approved | finding.go, README.md | | fixture | A `testdata` package that records expected findings and non-findings | | approved | CONTRIBUTING.md | -| golangci-lint | The Go linter runner that loads `ago` as a module plugin | | approved | README.md, plugin/golangci/plugin.go | +| golangci-lint | The Go linter runner that loads `goago` as a module plugin | | approved | README.md, plugin/golangci/plugin.go | | meta-name | The `enable` values `default` and `all` | | approved | README.md, config.go | | naked return | A `return` with no operands in a function with named results | | approved | README.md, rule_declarations.go | | near-miss | A neighbouring construct the rule must not report | | approved | CONTRIBUTING.md | @@ -19,10 +19,10 @@ when they match a row. | non-test | A `.go` file whose name does not end in `_test.go` | | approved | docs/stdlib-survey.md | | off by default | A rule that is disabled until configuration enables it | off-by-default | approved | README.md, CONTRIBUTING.md | | on by default | A rule that is enabled when no configuration selects a rule set | | approved | README.md | -| rationale | The explanation `ago -explain` prints for a rule | | approved | rule.go, README.md | +| rationale | The explanation `goago -explain` prints for a rule | | approved | rule.go, README.md | | restriction-only | Rejects legal Go constructs. Does not add syntax, rewrite code, or change semantics | | approved | README.md, AGENTS.md | | rule | One named restriction paired with a `go/analysis` analyzer | | approved | rule.go | | SARIF | Static Analysis Results Interchange Format 2.1.0 | | approved | README.md, sarif.go | -| stale ignore | An `//ago:ignore` directive that suppressed no finding in the run | | approved | README.md, check.go | +| stale ignore | An `//goago:ignore` directive that suppressed no finding in the run | | approved | README.md, check.go | | standard library | The Go standard library under `GOROOT/src` | stdlib | approved | README.md, docs/stdlib-survey.md | -| suppression | An `//ago:ignore` or `//ago:ignore-file` directive | | approved | README.md, ignore.go | +| suppression | An `//goago:ignore` or `//goago:ignore-file` directive | | approved | README.md, ignore.go | diff --git a/Makefile b/Makefile index 747a5d2..a17b3b6 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ -# ago Makefile +# goago Makefile MAKEFLAGS += --no-print-directory -BINARY_NAME = ago -MAIN_PATH = ./cmd/ago +BINARY_NAME = goago +MAIN_PATH = ./cmd/goago GO = go VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "v0.0.0-dev") -LDFLAGS = -ldflags "-s -w -X github.com/agentstation/ago.Version=$(VERSION)" +LDFLAGS = -ldflags "-s -w -X github.com/agentstation/goago.Version=$(VERSION)" GOLANGCI_LINT_VERSION = v2.12.2 GORELEASER_VERSION = 2.17.1 @@ -33,16 +33,16 @@ help: ## Display this help message ##@ Build .PHONY: build -build: ## Build the ago binary +build: ## Build the goago binary $(GO) build $(LDFLAGS) -o $(BINARY_NAME) $(MAIN_PATH) .PHONY: install -install: ## Install ago into GOBIN +install: ## Install goago into GOBIN $(GO) install $(LDFLAGS) $(MAIN_PATH) .PHONY: clean clean: ## Remove build and test output - @rm -f $(BINARY_NAME) coverage.txt ago.sarif + @rm -f $(BINARY_NAME) coverage.txt goago.sarif @rm -rf dist/ $(GO) clean -testcache @@ -122,12 +122,12 @@ fuzz: ## Run native Go fuzz tests for FUZZTIME ##@ Dogfood -.PHONY: ago -ago: build ## Run ago against its own source +.PHONY: goago +goago: build ## Run goago against its own source ./$(BINARY_NAME) ./... .PHONY: check -check: fmt-check vet test ago ## Run everything CI runs +check: fmt-check vet test goago ## Run everything CI runs ##@ Release diff --git a/README.md b/README.md index 6a1b8db..4f41bc3 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -# ago +# goago -[![CI](https://github.com/agentstation/ago/actions/workflows/ci.yml/badge.svg)](https://github.com/agentstation/ago/actions/workflows/ci.yml) -[![Go Reference](https://pkg.go.dev/badge/github.com/agentstation/ago.svg)](https://pkg.go.dev/github.com/agentstation/ago) -[![Go Report Card](https://goreportcard.com/badge/github.com/agentstation/ago)](https://goreportcard.com/report/github.com/agentstation/ago) +[![CI](https://github.com/agentstation/goago/actions/workflows/ci.yml/badge.svg)](https://github.com/agentstation/goago/actions/workflows/ci.yml) +[![Go Reference](https://pkg.go.dev/badge/github.com/agentstation/goago.svg)](https://pkg.go.dev/github.com/agentstation/goago) +[![Go Report Card](https://goreportcard.com/badge/github.com/agentstation/goago)](https://goreportcard.com/report/github.com/agentstation/goago) [![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](#license) **One way to write Go, no matter who writes it.** -The name has three readings: +Pronounce `goago` as **go ago**. The name keeps three meanings: - *agent Go*: the Go that coding agents may write. - *a Go*: one selected way to write Go for every human developer and coding agent in a project. -- *ago*: an earlier, smaller Go. It recalls the simpler language that inspired +- *Go ago*: an earlier, smaller Go. It recalls the simpler language that inspired the project, but it does not copy one past Go release. Go's original design called for [one way to write a piece of @@ -24,15 +24,15 @@ made the same point: > Rob Pike, [What We Got Right, What We Got > Wrong](https://commandcenter.blogspot.com/2024/01/what-we-got-right-what-we-got-wrong.html) -`gofmt` gives Go one format. ago enforces a project's selected way to write Go. +`gofmt` gives Go one format. goago enforces a project's selected way to write Go. Human developers, coding agents, and CI use the same policy. -`ago` rejects selected legal Go constructs. It does not add syntax, rewrite -code, or change semantics. Code that passes `ago` is ordinary Go that builds +`goago` rejects selected legal Go constructs. It does not add syntax, rewrite +code, or change semantics. Code that passes `goago` is ordinary Go that builds with the stock toolchain. ```console -$ go tool ago ./... +$ go tool goago ./... internal/store/index.go:42:2: naked return in indexAll; name the values you are returning (no-naked-return) internal/store/index.go:88:9: new() takes a type, not an expression (no-new-expr) 2 violations @@ -40,51 +40,53 @@ internal/store/index.go:88:9: new() takes a type, not an expression (no-new-expr Read the [design case](docs/design.md) for the project boundary and evidence. -## Adopt ago in a Go repository +Previously named `ago`. See [migration](docs/migration.md) to update an existing installation. -ago requires Go 1.25 or later and a Go module. +## Adopt goago in a Go repository -1. Add ago as a module tool dependency. +goago requires Go 1.25 or later and a Go module. + +1. Add goago as a module tool dependency. ```sh - go get -tool github.com/agentstation/ago/cmd/ago@latest + go get -tool github.com/agentstation/goago/cmd/goago@latest ``` - This command pins the ago version in `go.mod`. It records module checksums + This command pins the goago version in `go.mod`. It records module checksums in `go.sum`. 2. Check the module. ```sh - go tool ago ./... + go tool goago ./... ``` A clean run prints nothing and exits with status 0. -The Go module now owns the ago version. Developers, coding agents, and CI can -run `go tool ago` without a global installation or a `PATH` change. +The Go module now owns the goago version. Developers, coding agents, and CI can +run `go tool goago` without a global installation or a `PATH` change. -ago does not require a config file. The pinned ago version supplies the default -rule policy. Add `.ago.yml` only when the project needs a different policy. -Run the same `go get -tool` command later to upgrade ago deliberately. +goago does not require a config file. The pinned goago version supplies the default +rule policy. Add `.goago.yml` only when the project needs a different policy. +Run the same `go get -tool` command later to upgrade goago deliberately. ### Other installation methods Install a global command when one pinned repository does not own the use: ```sh -go install github.com/agentstation/ago/cmd/ago@latest +go install github.com/agentstation/goago/cmd/goago@latest ``` On macOS or Linux with Homebrew: ```sh -brew trust --cask agentstation/tap/ago -brew install --cask agentstation/tap/ago +brew trust --cask agentstation/tap/goago +brew install --cask agentstation/tap/goago ``` Release archives, checksums, and software bills of materials are available on -the [release page](https://github.com/agentstation/ago/releases). +the [release page](https://github.com/agentstation/goago/releases). ## Make the policy automatic @@ -93,16 +95,16 @@ policy: | File | Purpose | When needed | | --- | --- | --- | -| `go.mod` and `go.sum` | Pin the ago command and its module graph. | Always | -| `.ago.yml` | Change or record the built-in rule policy. | Only for a custom policy | -| `AGENTS.md` | Tell coding agents when and how to run ago. | Repositories that use coding agents | -| CI workflow | Reject a change that violates the policy. | Repositories that enforce ago | +| `go.mod` and `go.sum` | Pin the goago command and its module graph. | Always | +| `.goago.yml` | Change or record the built-in rule policy. | Only for a custom policy | +| `AGENTS.md` | Tell coding agents when and how to run goago. | Repositories that use coding agents | +| CI workflow | Reject a change that violates the policy. | Repositories that enforce goago | Add this instruction to the adopting repository's `AGENTS.md`: ```markdown -Run `go tool ago -stale-ignores -format json ./...` after each Go change. -Fix findings in source. Do not add or change `.ago.yml` only to make the run +Run `go tool goago -stale-ignores -format json ./...` after each Go change. +Fix findings in source. Do not add or change `.goago.yml` only to make the run pass. Do not add a suppression only to make the run pass. Exit status 2 means the check was incomplete. ``` @@ -113,47 +115,47 @@ Use the same pinned tool in GitHub Actions: - uses: actions/setup-go@v7 with: go-version: stable -- run: go tool ago -format github ./... +- run: go tool goago -format github ./... ``` CI remains the policy boundary. Agent instructions and the optional -[ago Agent Skill](#optional-agent-skill) improve the local repair loop. +[goago Agent Skill](#optional-agent-skill) improve the local repair loop. -## Run ago +## Run goago ```sh -go tool ago ./... # default rule set, current module -go tool ago -list # show every rule and which are on -go tool ago -explain no-goto # print one complete rationale -go tool ago -all ./... # run every rule -go tool ago -tests ./... # include _test.go files -go tool ago -stale-ignores ./... # report unused suppressions +go tool goago ./... # default rule set, current module +go tool goago -list # show every rule and which are on +go tool goago -explain no-goto # print one complete rationale +go tool goago -all ./... # run every rule +go tool goago -tests ./... # include _test.go files +go tool goago -stale-ignores ./... # report unused suppressions ``` Package arguments are [`go/packages`](https://pkg.go.dev/golang.org/x/tools/go/packages) -patterns. With no arguments, ago checks `./...`. +patterns. With no arguments, goago checks `./...`. -ago always skips `vendor/` and `testdata/`. Third-party code is not yours to +goago always skips `vendor/` and `testdata/`. Third-party code is not yours to restrict. | Exit status | Meaning | | --- | --- | | `0` | The run completed with no findings or stale ignores. | | `1` | The run found a rule violation or stale ignore. | -| `2` | ago could not complete a meaningful run. | +| `2` | goago could not complete a meaningful run. | ## Configure the rule policy -Configuration is optional. With no config file, ago runs the default rules from +Configuration is optional. With no config file, goago runs the default rules from the version pinned in `go.mod`. Create a minimal policy only when the project needs one: ```sh -go tool ago -init +go tool goago -init ``` -The command writes `.ago.yml` at the nearest `go.mod` or `go.work` root. It +The command writes `.goago.yml` at the nearest `go.mod` or `go.work` root. It refuses to create a second policy when a parent policy already applies. ```yaml @@ -178,11 +180,11 @@ Choose the policy form that matches the project: | Form | Upgrade behavior | | --- | --- | -| No `.ago.yml` | Use the defaults in the pinned ago version. | +| No `.goago.yml` | Use the defaults in the pinned goago version. | | `enable: [default]` | Record a policy file and use the defaults in the pinned version. | | Explicit rule names | Keep the named rule set until the project edits the file. | -ago matches each `exclude` pattern against three path shapes: +goago matches each `exclude` pattern against three path shapes: - the complete slash-separated path. - each path element. @@ -193,23 +195,23 @@ depth, and `third_party/*` matches that subtree. Unknown keys and unknown rule names stop the run. A policy typo cannot disable a rule silently. Use `-config path` to name a file. Use `-no-config` to ignore -all policy files. The [JSON Schema](ago.schema.json) supplies editor validation. -ago also accepts unversioned files created by v0.1. +all policy files. The [JSON Schema](goago.schema.json) supplies editor validation. +goago also accepts unversioned files created by v0.1. ## Fix or suppress a finding Fix source code when the selected policy applies. Each finding includes the -rule name. Run `go tool ago -explain ` for the full rationale and rule +rule name. Run `go tool goago -explain ` for the full rationale and rule boundary. Use a suppression only when the local construct is a justified exception: ```go -//ago:ignore no-goto -- hand-written state machine, see docs/parser.md +//goago:ignore no-goto -- hand-written state machine, see docs/parser.md goto retry ``` -The directive applies to the next line. A top-level `//ago:ignore-file` +The directive applies to the next line. A top-level `//goago:ignore-file` directive applies to its file. Both forms accept a comma-separated rule list or `*`. @@ -219,13 +221,13 @@ directive suppresses nothing, and `no-invalid-ignore` reports it. Run with ## Machine contract for coding agents -ago exposes policy and results as stable data. A coding agent does not need to +goago exposes policy and results as stable data. A coding agent does not need to parse this README. Discover the active rules: ```sh -go tool ago -list -format json +go tool goago -list -format json ``` The document includes a schema version and the resolved policy source. It also @@ -239,7 +241,7 @@ true when the command used `-no-config`. Read findings and incomplete-run errors: ```sh -go tool ago -stale-ignores -format json ./... +go tool goago -stale-ignores -format json ./... ``` ```json @@ -257,7 +259,7 @@ go tool ago -stale-ignores -format json ./... "column": 2, "endLine": 42, "endColumn": 8, - "docURL": "https://github.com/agentstation/ago/blob/main/docs/rules.md#no-naked-return" + "docURL": "https://github.com/agentstation/goago/blob/main/docs/rules.md#no-naked-return" } ], "staleIgnores": [], @@ -265,11 +267,11 @@ go tool ago -stale-ignores -format json ./... } ``` -ago sorts and deduplicates findings. The same version, policy, and source tree +goago sorts and deduplicates findings. The same version, policy, and source tree produce the same JSON document. Existing JSON fields keep their names and meanings. Later versions can add fields. -A package load or parse failure appears in `errors`. ago continues with each +A package load or parse failure appears in `errors`. goago continues with each package that it can analyze. Exit status 2 means that no usable result was available, so an empty finding list is not a clean result. @@ -279,7 +281,7 @@ The optional skill teaches compatible coding agents the discovery, repair, suppression, and verification loop: ```sh -gh skill install agentstation/skills ago --agent codex --scope project +gh skill install agentstation/skills goago --agent codex --scope project ``` Change `--agent` for another supported host. The skill guides the local repair @@ -308,7 +310,7 @@ Six rules are off by default because they encode a project-specific choice. | [`no-blank-import-outside-main`](docs/rules.md#no-blank-import-outside-main) | off | Only package `main` can use a blank import. | The [rule reference](docs/rules.md) gives the rationale, replacement, evidence, -and non-findings for each rule. `go tool ago -list -format json` carries the +and non-findings for each rule. `go tool goago -list -format json` carries the same rule catalogue in machine-readable form. ## Integrations @@ -318,17 +320,17 @@ same rule catalogue in machine-readable form. Use workflow-command output for inline pull request annotations: ```sh -go tool ago -format github ./... +go tool goago -format github ./... ``` Use SARIF 2.1.0 for GitHub code scanning or another SARIF consumer: ```yaml -- run: go tool ago -format sarif ./... > ago.sarif +- run: go tool goago -format sarif ./... > goago.sarif continue-on-error: true - uses: github/codeql-action/upload-sarif@v4 with: - sarif_file: ago.sarif + sarif_file: goago.sarif ``` ### Go analysis drivers @@ -340,59 +342,59 @@ them with `multichecker`: package main import ( - "github.com/agentstation/ago" + "github.com/agentstation/goago" "golang.org/x/tools/go/analysis/multichecker" ) func main() { - multichecker.Main(ago.Analyzers()...) + multichecker.Main(goago.Analyzers()...) } ``` A binary built with `multichecker` supports `go vet -vettool`. The shipped -`cmd/ago` command uses its own policy, JSON, suppression, and exit contracts. +`cmd/goago` command uses its own policy, JSON, suppression, and exit contracts. It is not a vet tool. Library callers can inspect rules or run the checker directly: ```go -rules := ago.Rules() -rule, ok := ago.Lookup("no-goto") -report, err := ago.Check(ago.Options{Patterns: []string{"./..."}}) +rules := goago.Rules() +rule, ok := goago.Lookup("no-goto") +report, err := goago.Check(goago.Options{Patterns: []string{"./..."}}) ``` -See the [package documentation](https://pkg.go.dev/github.com/agentstation/ago) +See the [package documentation](https://pkg.go.dev/github.com/agentstation/goago) for the complete API. ### golangci-lint -ago ships as a [golangci-lint module +goago ships as a [golangci-lint module plugin](https://golangci-lint.run/plugins/module-plugins/). Add it to `.custom-gcl.yml`: ```yaml version: v2.12.2 plugins: - - module: github.com/agentstation/ago - import: github.com/agentstation/ago/plugin/golangci + - module: github.com/agentstation/goago + import: github.com/agentstation/goago/plugin/golangci version: latest ``` -Run `golangci-lint custom`, then enable the `ago` custom linter in +Run `golangci-lint custom`, then enable the `goago` custom linter in `.golangci.yml`. Pin the plugin version before you commit the configuration. ## Project - [Design and scope](docs/design.md) - [Rule reference](docs/rules.md) -- [Config schema](ago.schema.json) +- [Config schema](goago.schema.json) - [Contributing](CONTRIBUTING.md) - [Security policy](SECURITY.md) - [Changelog](CHANGELOG.md) ## License -ago is available under either license, at your option: +goago is available under either license, at your option: - Apache License 2.0 ([LICENSE-APACHE](LICENSE-APACHE)) - MIT License ([LICENSE-MIT](LICENSE-MIT)) diff --git a/SECURITY.md b/SECURITY.md index a72a050..86f85e4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,11 +7,11 @@ older releases do not receive separate security support. ## Reporting a vulnerability -Open a private advisory at https://github.com/agentstation/ago/security/advisories/new. +Open a private advisory at https://github.com/agentstation/goago/security/advisories/new. Email `security@agentstation.ai`. Do not open a public issue for a suspected vulnerability. -Include the affected version (`ago -version`), the Go toolchain version, the +Include the affected version (`goago -version`), the Go toolchain version, the command you ran, a minimal reproduction, and the security effect. Remove any proprietary source from your report. A synthetic reproduction is always preferable. @@ -22,14 +22,14 @@ before that coordination is complete. ## Threat model -`ago` is a static analyzer. It reads Go source and configuration and writes a +`goago` is a static analyzer. It reads Go source and configuration and writes a report. It executes no code from the packages it analyzes. In scope: -- A crafted `.ago.yml` or `//ago:ignore` directive that causes `ago` to +- A crafted `.goago.yml` or `//goago:ignore` directive that causes `goago` to crash, hang, or consume unbounded memory. -- A crafted source file that causes `ago` to silently skip analysis. A +- A crafted source file that causes `goago` to silently skip analysis. A violation then goes unreported while the exit status stays `0`. - Output injection. A finding message, file path, or rule name that escapes SARIF or GitHub encoding and forges an alert. @@ -37,16 +37,16 @@ In scope: Out of scope: -- `ago` invokes the Go toolchain through `golang.org/x/tools/go/packages`, - which loads and builds package metadata. Running `ago` on untrusted source +- `goago` invokes the Go toolchain through `golang.org/x/tools/go/packages`, + which loads and builds package metadata. Running `goago` on untrusted source is equivalent to running `go list` on it, and carries the same risk. Do not - run `ago` on source you would not run `go build` on. + run `goago` on source you would not run `go build` on. - Vulnerabilities in the Go toolchain or in `golang.org/x/tools`. Report those upstream. We will pick up the fix on the next release. - A rule producing a false positive or a false negative. That is a correctness bug. Please file it as a normal issue. -ago rejects a `.ago.yml` larger than 1 MiB and an `exclude` list longer than +goago rejects a `.goago.yml` larger than 1 MiB and an `exclude` list longer than 1024 patterns. A config that excludes every package is an error, not a clean run. diff --git a/check.go b/check.go index 94f22b0..4e70854 100644 --- a/check.go +++ b/check.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "fmt" @@ -13,7 +13,7 @@ import ( // Options control one [Check] run. type Options struct { - // Dir is the directory ago resolves patterns against. An empty Dir means + // Dir is the directory goago resolves patterns against. An empty Dir means // the process working directory. Dir string // Patterns are go/packages patterns such as "./..." or a list of .go @@ -21,11 +21,11 @@ type Options struct { Patterns []string // Rules are the rules to run. An empty Rules means the default set. Rules []Rule - // Tests reports whether ago analyzes _test.go files. + // Tests reports whether goago analyzes _test.go files. Tests bool // Config supplies exclude patterns. It may be nil. Config *Config - // ReportStaleIgnores reports //ago:ignore directives that suppressed + // ReportStaleIgnores reports //goago:ignore directives that suppressed // nothing. ReportStaleIgnores bool } @@ -129,7 +129,7 @@ func Check(opts Options) (*Report, error) { return report, nil } -// toFinding converts an analysis diagnostic into ago's flat finding form, +// toFinding converts an analysis diagnostic into goago's flat finding form, // making the path relative to the run directory so that output is stable // across machines. func toFinding(pkg *packages.Package, rule Rule, d analysis.Diagnostic, dir string) Finding { @@ -151,7 +151,7 @@ func toFinding(pkg *packages.Package, rule Rule, d analysis.Diagnostic, dir stri } } -// collectStaleIgnores finds every //ago:ignore directive that suppressed +// collectStaleIgnores finds every //goago:ignore directive that suppressed // nothing during the run. func collectStaleIgnores(graph *checker.Graph, dir string) []StaleIgnore { out := []StaleIgnore{} diff --git a/check_test.go b/check_test.go index b9f5013..fe85356 100644 --- a/check_test.go +++ b/check_test.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "path/filepath" diff --git a/cmd/ago/json.go b/cmd/goago/json.go similarity index 100% rename from cmd/ago/json.go rename to cmd/goago/json.go diff --git a/cmd/ago/main.go b/cmd/goago/main.go similarity index 75% rename from cmd/ago/main.go rename to cmd/goago/main.go index 2b7b49f..3ca6213 100644 --- a/cmd/ago/main.go +++ b/cmd/goago/main.go @@ -1,18 +1,18 @@ -// Command ago enforces one way to write Go across a codebase. +// Command goago enforces one way to write Go across a codebase. // // A project selects the Go constructs that it accepts. Developers and coding // agents use the same rule policy, and CI enforces it. // -// ago only ever rejects language constructs. It never adds syntax, never -// rewrites code, and never changes semantics. Code that passes ago is +// goago only ever rejects language constructs. It never adds syntax, never +// rewrites code, and never changes semantics. Code that passes goago is // ordinary Go that builds with the stock toolchain. // // Usage: // -// ago [flags] [packages] +// goago [flags] [packages] // // The package arguments are go/packages patterns, the same ones go build and -// go vet accept. With no arguments ago checks ./... under the working +// go vet accept. With no arguments goago checks ./... under the working // directory. // // Flags: @@ -29,13 +29,13 @@ // // -format f text, json, sarif, or github (default text). // -// -config path Read this config instead of searching for .ago.yml. +// -config path Read this config instead of searching for .goago.yml. // -// -no-config Ignore any .ago.yml on disk. +// -no-config Ignore any .goago.yml on disk. // -// -init Write an optional .ago.yml at the project root. +// -init Write an optional .goago.yml at the project root. // -// -stale-ignores Report //ago:ignore directives that suppressed nothing. +// -stale-ignores Report //goago:ignore directives that suppressed nothing. // // -version Print the version and exit. // @@ -43,7 +43,7 @@ // // 0 No violations. // 1 At least one violation. -// 2 ago could not complete the run. +// 2 goago could not complete the run. // // A load or type error does not by itself abort the run. The command reports // what it could analyze and exits 2 only when it produced no usable result. @@ -59,7 +59,7 @@ import ( "sort" "strings" - "github.com/agentstation/ago" + "github.com/agentstation/goago" ) // Exit statuses. They are part of the command's contract with CI and with @@ -75,7 +75,7 @@ func main() { } func run(args []string, stdout, stderr io.Writer) int { - fs := flag.NewFlagSet("ago", flag.ContinueOnError) + fs := flag.NewFlagSet("goago", flag.ContinueOnError) fs.SetOutput(stderr) var ( listFlag = fs.Bool("list", false, "list the rule set and exit") @@ -84,10 +84,10 @@ func run(args []string, stdout, stderr io.Writer) int { allFlag = fs.Bool("all", false, "run every rule") testsFlag = fs.Bool("tests", false, "also check _test.go files") formatFlag = fs.String("format", "text", "output format: text, json, sarif, or github") - configFlag = fs.String("config", "", "read this config instead of searching for .ago.yml") - noConfig = fs.Bool("no-config", false, "ignore any .ago.yml on disk") - initFlag = fs.Bool("init", false, "write an optional .ago.yml at the project root and exit") - staleFlag = fs.Bool("stale-ignores", false, "report //ago:ignore directives that suppressed nothing") + configFlag = fs.String("config", "", "read this config instead of searching for .goago.yml") + noConfig = fs.Bool("no-config", false, "ignore any .goago.yml on disk") + initFlag = fs.Bool("init", false, "write an optional .goago.yml at the project root and exit") + staleFlag = fs.Bool("stale-ignores", false, "report //goago:ignore directives that suppressed nothing") versionFlag = fs.Bool("version", false, "print the version and exit") ) fs.Usage = func() { usage(stdout, fs) } @@ -101,7 +101,7 @@ func run(args []string, stdout, stderr io.Writer) int { switch { case *versionFlag: - fmt.Fprintf(stdout, "ago %s\n", ago.Version) + fmt.Fprintf(stdout, "goago %s\n", goago.Version) return exitClean case *initFlag: return writeInitConfig(".", stdout, stderr) @@ -109,29 +109,29 @@ func run(args []string, stdout, stderr io.Writer) int { return explain(stdout, stderr, *explainFlag) } - format, err := ago.ParseFormat(*formatFlag) + format, err := goago.ParseFormat(*formatFlag) if err != nil { - fmt.Fprintf(stderr, "ago: %v\n", err) + fmt.Fprintf(stderr, "goago: %v\n", err) return exitError } - cfg := &ago.Config{} + cfg := &goago.Config{} if !*noConfig { - cfg, err = ago.LoadConfig(".", *configFlag) + cfg, err = goago.LoadConfig(".", *configFlag) if err != nil { - fmt.Fprintf(stderr, "ago: %v\n", err) + fmt.Fprintf(stderr, "goago: %v\n", err) return exitError } } overrides, err := ruleOverrides(*rulesFlag, *allFlag) if err != nil { - fmt.Fprintf(stderr, "ago: %v\n", err) + fmt.Fprintf(stderr, "goago: %v\n", err) return exitError } rules := cfg.Enabled(overrides) if len(rules) == 0 { - fmt.Fprintln(stderr, "ago: no rules enabled; check the rule flags or policy config") + fmt.Fprintln(stderr, "goago: no rules enabled; check the rule flags or policy config") return exitError } @@ -140,7 +140,7 @@ func run(args []string, stdout, stderr io.Writer) int { return exitClean } - report, err := ago.Check(ago.Options{ + report, err := goago.Check(goago.Options{ Patterns: fs.Args(), Rules: rules, Tests: *testsFlag || cfg.Tests, @@ -148,32 +148,32 @@ func run(args []string, stdout, stderr io.Writer) int { ReportStaleIgnores: *staleFlag, }) if err != nil { - fmt.Fprintf(stderr, "ago: %v\n", err) + fmt.Fprintf(stderr, "goago: %v\n", err) return exitError } if err := report.Write(stdout, format); err != nil { - fmt.Fprintf(stderr, "ago: writing report: %v\n", err) + fmt.Fprintf(stderr, "goago: writing report: %v\n", err) return exitError } // Load errors go to stderr so that they never contaminate a machine-read // stdout, and are already carried in the JSON and SARIF documents. - if format == ago.FormatText || format == ago.FormatGitHub { + if format == goago.FormatText || format == goago.FormatGitHub { for _, e := range report.Errors { - fmt.Fprintf(stderr, "ago: %s\n", e) + fmt.Fprintf(stderr, "goago: %s\n", e) } } switch { case len(report.Findings) > 0 || len(report.StaleIgnores) > 0: - if format == ago.FormatText { + if format == goago.FormatText { fmt.Fprintf(stderr, "\n%s\n", summary(report)) } return exitViolations case len(report.Errors) > 0 && len(report.Rules) > 0 && reportedNothing(report): // Nothing was analyzable. Exiting 0 here would report a clean tree // that was never actually checked. - fmt.Fprintln(stderr, "ago: no package could be analyzed") + fmt.Fprintln(stderr, "goago: no package could be analyzed") return exitError default: return exitClean @@ -182,12 +182,12 @@ func run(args []string, stdout, stderr io.Writer) int { // reportedNothing reports whether the run produced no findings at all, which // combined with load errors means the run was not meaningful. -func reportedNothing(r *ago.Report) bool { +func reportedNothing(r *goago.Report) bool { return len(r.Findings) == 0 && len(r.StaleIgnores) == 0 } // summary renders the one-line tally printed after text output. -func summary(r *ago.Report) string { +func summary(r *goago.Report) string { var parts []string if n := len(r.Findings); n > 0 { parts = append(parts, fmt.Sprintf("%d violation%s", n, plural(n))) @@ -226,9 +226,9 @@ func ruleOverrides(rulesFlag string, all bool) ([]string, error) { out = append(out, name) continue } - r, ok := ago.Lookup(name) + r, ok := goago.Lookup(name) if !ok { - return nil, fmt.Errorf("unknown rule %q; run \"ago -list\" for the rule set", name) + return nil, fmt.Errorf("unknown rule %q; run \"goago -list\" for the rule set", name) } out = append(out, r.Name) } @@ -236,14 +236,14 @@ func ruleOverrides(rulesFlag string, all bool) ([]string, error) { } // listRules prints the rule set. In text form an asterisk marks the enabled -// rules, so "ago -list" doubles as a check on what the current config +// rules, so "goago -list" doubles as a check on what the current config // resolves to. -func listRules(stdout io.Writer, format ago.Format, enabled []ago.Rule, policy policyJSON) { +func listRules(stdout io.Writer, format goago.Format, enabled []goago.Rule, policy policyJSON) { on := map[string]bool{} for _, r := range enabled { on[r.Name] = true } - if format == ago.FormatJSON { + if format == goago.FormatJSON { writeRulesJSON(stdout, on, policy) return } @@ -263,12 +263,12 @@ func listRules(stdout io.Writer, format ago.Format, enabled []ago.Rule, policy p } fmt.Fprintf(stdout, "Tests: %t\nExcludes: %d\n\n", policy.Tests, len(policy.Exclude)) width := 0 - for _, r := range ago.Rules() { + for _, r := range goago.Rules() { if len(r.Name) > width { width = len(r.Name) } } - for _, r := range ago.Rules() { + for _, r := range goago.Rules() { mark := " " if on[r.Name] { mark = "*" @@ -279,8 +279,8 @@ func listRules(stdout io.Writer, format ago.Format, enabled []ago.Rule, policy p } fmt.Fprintf(stdout, "%s %-*s %s%s\n", mark, width, r.Name, r.Summary, reverts) } - fmt.Fprintf(stdout, "\n* = enabled for this run (%d of %d)\n", len(enabled), len(ago.Rules())) - fmt.Fprintln(stdout, `Run "ago -explain " for the full rationale.`) + fmt.Fprintf(stdout, "\n* = enabled for this run (%d of %d)\n", len(enabled), len(goago.Rules())) + fmt.Fprintln(stdout, `Run "goago -explain " for the full rationale.`) } // ruleJSON is the -list -format json schema. It is what a coding agent reads @@ -307,7 +307,7 @@ type policyJSON struct { Exclude []string `json:"exclude"` } -func resolvedPolicy(cfg *ago.Config, overrides []string, noConfig, testsFlag bool) policyJSON { +func resolvedPolicy(cfg *goago.Config, overrides []string, noConfig, testsFlag bool) policyJSON { source := "built-in" if cfg.Path() != "" { source = "config" @@ -337,8 +337,8 @@ func writeRulesJSON(stdout io.Writer, enabled map[string]bool, policy policyJSON Version string `json:"version"` Policy policyJSON `json:"policy"` Rules []ruleJSON `json:"rules"` - }{SchemaVersion: ruleCatalogueSchemaVersion, Version: ago.Version, Policy: policy} - for _, r := range ago.Rules() { + }{SchemaVersion: ruleCatalogueSchemaVersion, Version: goago.Version, Policy: policy} + for _, r := range goago.Rules() { out.Rules = append(out.Rules, ruleJSON{ Name: r.Name, Analyzer: r.Analyzer.Name, @@ -358,9 +358,9 @@ const ruleCatalogueSchemaVersion = 1 // explain prints one rule's full rationale. func explain(stdout, stderr io.Writer, name string) int { - r, ok := ago.Lookup(name) + r, ok := goago.Lookup(name) if !ok { - fmt.Fprintf(stderr, "ago: unknown rule %q\n", name) + fmt.Fprintf(stderr, "goago: unknown rule %q\n", name) suggest(stderr, name) return exitError } @@ -380,42 +380,42 @@ func explain(stdout, stderr io.Writer, name string) int { // suggest prints the rules whose names share a prefix with a misspelling. func suggest(stderr io.Writer, name string) { var near []string - for _, n := range ago.Names() { + for _, n := range goago.Names() { if strings.Contains(n, name) || strings.Contains(name, n) { near = append(near, n) } } sort.Strings(near) if len(near) > 0 { - fmt.Fprintf(stderr, "ago: did you mean %s?\n", strings.Join(near, ", ")) + fmt.Fprintf(stderr, "goago: did you mean %s?\n", strings.Join(near, ", ")) return } - fmt.Fprintln(stderr, `ago: run "ago -list" for the rule set`) + fmt.Fprintln(stderr, `goago: run "goago -list" for the rule set`) } // writeInitConfig writes a minimal policy at the nearest Go module or // workspace root. It refuses to create a second policy below one that already // applies. func writeInitConfig(dir string, stdout, stderr io.Writer) int { - cfg, err := ago.LoadConfig(dir, "") + cfg, err := goago.LoadConfig(dir, "") if err != nil { - fmt.Fprintf(stderr, "ago: %v\n", err) + fmt.Fprintf(stderr, "goago: %v\n", err) return exitError } if cfg.Path() != "" { - fmt.Fprintf(stderr, "ago: policy already exists: %s\n", cfg.Path()) + fmt.Fprintf(stderr, "goago: policy already exists: %s\n", cfg.Path()) return exitError } root, marker, err := findProjectRoot(dir) if err != nil { - fmt.Fprintf(stderr, "ago: %v\n", err) + fmt.Fprintf(stderr, "goago: %v\n", err) return exitError } - path := filepath.Join(root, ago.ConfigName) + path := filepath.Join(root, goago.ConfigName) // A config the whole team commits and reads is 0644. f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644) //nolint:gosec // team-readable by design if err != nil { - fmt.Fprintf(stderr, "ago: %v\n", err) + fmt.Fprintf(stderr, "goago: %v\n", err) return exitError } removeOnError := true @@ -424,13 +424,13 @@ func writeInitConfig(dir string, stdout, stderr io.Writer) int { _ = os.Remove(path) } }() - if _, err := io.WriteString(f, ago.ExampleConfig()); err != nil { + if _, err := io.WriteString(f, goago.ExampleConfig()); err != nil { _ = f.Close() - fmt.Fprintf(stderr, "ago: writing %s: %v\n", path, err) + fmt.Fprintf(stderr, "goago: writing %s: %v\n", path, err) return exitError } if err := f.Close(); err != nil { - fmt.Fprintf(stderr, "ago: writing %s: %v\n", path, err) + fmt.Fprintf(stderr, "goago: writing %s: %v\n", path, err) return exitError } removeOnError = false @@ -459,13 +459,13 @@ func findProjectRoot(dir string) (string, string, error) { } func usage(w io.Writer, fs *flag.FlagSet) { - fmt.Fprint(w, `ago enforces one way to write Go across a codebase. + fmt.Fprint(w, `goago enforces one way to write Go across a codebase. Usage: - ago [flags] [packages] + goago [flags] [packages] Package arguments are go/packages patterns, the same ones go build accepts. -With no arguments ago checks ./... under the working directory. +With no arguments goago checks ./... under the working directory. Flags: `) @@ -475,8 +475,8 @@ Flags: Exit status: 0 no violations 1 at least one violation - 2 ago could not complete the run + 2 goago could not complete the run -Docs: https://github.com/agentstation/ago +Docs: https://github.com/agentstation/goago `) } diff --git a/cmd/ago/main_test.go b/cmd/goago/main_test.go similarity index 81% rename from cmd/ago/main_test.go rename to cmd/goago/main_test.go index 4053cdc..e473e01 100644 --- a/cmd/ago/main_test.go +++ b/cmd/goago/main_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "github.com/agentstation/ago" + "github.com/agentstation/goago" ) func TestHelpWritesOneCompleteDocumentToStdout(t *testing.T) { @@ -42,15 +42,15 @@ func TestInitWritesMinimalPolicyAtModuleRoot(t *testing.T) { if status := writeInitConfig(nested, &stdout, &stderr); status != exitClean { t.Fatalf("status = %d, stderr = %q", status, stderr.String()) } - path := filepath.Join(root, ago.ConfigName) + path := filepath.Join(root, goago.ConfigName) b, err := os.ReadFile(path) if err != nil { t.Fatal(err) } - if string(b) != ago.ExampleConfig() { + if string(b) != goago.ExampleConfig() { t.Errorf("config differs from ExampleConfig:\n%s", b) } - if _, err := os.Stat(filepath.Join(nested, ago.ConfigName)); !os.IsNotExist(err) { + if _, err := os.Stat(filepath.Join(nested, goago.ConfigName)); !os.IsNotExist(err) { t.Errorf("nested config exists or stat failed: %v", err) } if !strings.Contains(stdout.String(), "at go.mod root") { @@ -61,7 +61,7 @@ func TestInitWritesMinimalPolicyAtModuleRoot(t *testing.T) { func TestInitRejectsAnExistingParentPolicy(t *testing.T) { root := t.TempDir() writeTestFile(t, filepath.Join(root, "go.mod"), "module example.com/project\n\ngo 1.25\n") - policy := filepath.Join(root, ago.ConfigName) + policy := filepath.Join(root, goago.ConfigName) writeTestFile(t, policy, "enable: [default]\n") nested := filepath.Join(root, "internal", "example") if err := os.MkdirAll(nested, 0o755); err != nil { @@ -88,6 +88,24 @@ func TestInitWritesAtWorkspaceRoot(t *testing.T) { } } +func TestInitRejectsLegacyPolicy(t *testing.T) { + for _, name := range []string{".ago.yml", ".ago.yaml"} { + t.Run(name, func(t *testing.T) { + root := t.TempDir() + writeTestFile(t, filepath.Join(root, "go.mod"), "module example.com/project\n\ngo 1.25\n") + policy := filepath.Join(root, name) + writeTestFile(t, policy, "enable: [no-goto]\n") + var stdout, stderr bytes.Buffer + if status := writeInitConfig(root, &stdout, &stderr); status != exitError || !strings.Contains(stderr.String(), policy) { + t.Fatalf("status = %d, stderr = %q", status, stderr.String()) + } + if _, err := os.Stat(filepath.Join(root, goago.ConfigName)); !os.IsNotExist(err) { + t.Fatalf("new policy created or stat failed: %v", err) + } + }) + } +} + func TestInitRequiresModuleOrWorkspace(t *testing.T) { var stdout, stderr bytes.Buffer if status := writeInitConfig(t.TempDir(), &stdout, &stderr); status != exitError { @@ -100,7 +118,7 @@ func TestInitRequiresModuleOrWorkspace(t *testing.T) { func TestListJSONReportsResolvedPolicy(t *testing.T) { configDir := t.TempDir() - configPath := filepath.Join(configDir, ago.ConfigName) + configPath := filepath.Join(configDir, goago.ConfigName) writeTestFile(t, configPath, "version: 1\nenable: [default]\ntests: false\nexclude: [generated]\n") tests := []struct { @@ -137,8 +155,8 @@ func TestListJSONReportsResolvedPolicy(t *testing.T) { if got.Policy.RuleSource != tt.wantSource || got.Policy.ConfigPath != tt.wantPath || got.Policy.ConfigVersion != tt.wantVersion || got.Policy.ConfigDisabled != tt.wantDisabled || got.Policy.Tests != tt.wantTests || !slices.Equal(got.Policy.Exclude, tt.wantExclude) { t.Errorf("policy = %+v, want source=%q path=%q version=%d disabled=%t tests=%t exclude=%v", got.Policy, tt.wantSource, tt.wantPath, tt.wantVersion, tt.wantDisabled, tt.wantTests, tt.wantExclude) } - if len(got.Rules) != len(ago.Rules()) { - t.Errorf("rules = %d, want %d", len(got.Rules), len(ago.Rules())) + if len(got.Rules) != len(goago.Rules()) { + t.Errorf("rules = %d, want %d", len(got.Rules), len(goago.Rules())) } }) } diff --git a/config.go b/config.go index ce06010..967e7b7 100644 --- a/config.go +++ b/config.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "bytes" @@ -13,13 +13,13 @@ import ( yaml "go.yaml.in/yaml/v3" ) -// ConfigName is the file ago looks for when the command line names no +// ConfigName is the file goago looks for when the command line names no // configuration. ConfigNameAlt is an equivalent spelling. const ( - ConfigName = ".ago.yml" - ConfigNameAlt = ".ago.yaml" + ConfigName = ".goago.yml" + ConfigNameAlt = ".goago.yaml" - // maxConfigBytes is the largest config file ago will load. A larger file + // maxConfigBytes is the largest config file goago will load. A larger file // is an attack or a mistake, not a rule policy. maxConfigBytes = 1 << 20 // maxExcludePatterns caps Config.Exclude. @@ -41,19 +41,19 @@ type Config struct { // the default rule set and "all" expands to every rule. An empty list // means the default set. Enable []string `yaml:"enable"` - // Disable lists rules to turn off after ago applies Enable. + // Disable lists rules to turn off after goago applies Enable. Disable []string `yaml:"disable"` - // Tests reports whether ago checks _test.go files. + // Tests reports whether goago checks _test.go files. Tests bool `yaml:"tests"` - // Exclude lists path patterns. ago matches them with [path/filepath.Match] + // Exclude lists path patterns. goago matches them with [path/filepath.Match] // against each path element and skips those packages. Exclude []string `yaml:"exclude"` - // path records where ago loaded the config, for error messages. + // path records where goago loaded the config, for error messages. path string } -// Path returns the file ago loaded the config from, or "" for a default +// Path returns the file goago loaded the config from, or "" for a default // config. func (c *Config) Path() string { return c.path } @@ -64,12 +64,16 @@ const ( ) // LoadConfig reads a config file. A path of "" searches dir and each parent -// directory for .ago.yml, stopping at the filesystem root. When the search +// directory for .goago.yml or .goago.yaml, including the legacy .ago names. +// Multiple policy files in one directory are an error. When the search // finds no file it returns the default config and a nil error. func LoadConfig(dir, path string) (*Config, error) { if path == "" { - found, ok := findConfig(dir) - if !ok { + found, err := findConfig(dir) + if err != nil { + return nil, err + } + if found == "" { return &Config{}, nil } path = found @@ -113,21 +117,37 @@ func readConfigBytes(path string) ([]byte, error) { } // findConfig walks up from dir looking for a config file. -func findConfig(dir string) (string, bool) { +func findConfig(dir string) (string, error) { abs, err := filepath.Abs(dir) if err != nil { - return "", false + return "", err } for { - for _, name := range []string{ConfigName, ConfigNameAlt} { + var found string + // Keep existing policies active after the command rename. + for _, name := range []string{ConfigName, ConfigNameAlt, ".ago.yml", ".ago.yaml"} { p := filepath.Join(abs, name) - if fi, err := os.Stat(p); err == nil && !fi.IsDir() { - return p, true + fi, err := os.Stat(p) + if os.IsNotExist(err) { + continue } + if err != nil { + return "", err + } + if fi.IsDir() { + continue + } + if found != "" { + return "", fmt.Errorf("multiple policy files: %s and %s; keep one or use -config", found, p) + } + found = p + } + if found != "" { + return found, nil } parent := filepath.Dir(abs) if parent == abs { - return "", false + return "", nil } abs = parent } @@ -230,7 +250,7 @@ func (c *Config) Enabled(overrides []string) []Rule { // Skip reports whether a file path matches any exclude pattern. // -// ago matches a pattern three ways, because each way catches a different +// goago matches a pattern three ways, because each way catches a different // surprise. It matches the whole slash-separated path, so "*.pb.go" works. // It matches each path element, so "generated" excludes any directory with // that name at any depth. It matches each leading path prefix, so @@ -256,13 +276,13 @@ func (c *Config) Skip(path string) bool { return false } -// ExampleConfig returns the minimal policy that "ago -init" writes. The -// "default" meta-name follows the defaults in the pinned ago version. +// ExampleConfig returns the minimal policy that "goago -init" writes. The +// "default" meta-name follows the defaults in the pinned goago version. func ExampleConfig() string { - return `# yaml-language-server: $schema=https://raw.githubusercontent.com/agentstation/ago/main/ago.schema.json -# ago uses its built-in defaults when this file is absent. -# "default" follows the defaults in the ago version pinned by go.mod. -# Run "go tool ago -list" to inspect the resolved policy. + return `# yaml-language-server: $schema=https://raw.githubusercontent.com/agentstation/goago/main/goago.schema.json +# goago uses its built-in defaults when this file is absent. +# "default" follows the defaults in the goago version pinned by go.mod. +# Run "go tool goago -list" to inspect the resolved policy. version: 1 enable: @@ -272,7 +292,7 @@ disable: [] # Check _test.go files as well as production files. tests: false -# Path patterns to skip. ago always skips vendor and testdata. +# Path patterns to skip. goago always skips vendor and testdata. exclude: [] ` } diff --git a/config_test.go b/config_test.go index 5a65483..fc77c44 100644 --- a/config_test.go +++ b/config_test.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "encoding/json" @@ -191,7 +191,7 @@ func TestConfigSkip(t *testing.T) { } } -// TestExampleConfigIsValid keeps "ago -init" honest: the file it writes must +// TestExampleConfigIsValid keeps "goago -init" honest: the file it writes must // load without error and select exactly the default rule set. func TestExampleConfigIsValid(t *testing.T) { dir := t.TempDir() @@ -212,7 +212,7 @@ func TestExampleConfigIsValid(t *testing.T) { } func TestConfigSchemaRuleNames(t *testing.T) { - b, err := os.ReadFile("ago.schema.json") + b, err := os.ReadFile("goago.schema.json") if err != nil { t.Fatal(err) } diff --git a/docs/design.md b/docs/design.md index 46fa34d..31ba671 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,7 +1,7 @@ # Design and scope -ago enforces one way to write Go across a codebase. A project selects the Go -constructs that it accepts. ago enforces that choice. +goago enforces one way to write Go across a codebase. A project selects the Go +constructs that it accepts. goago enforces that choice. ## Motivation @@ -16,7 +16,7 @@ Rob Pike later made the same point: > Rob Pike, [What We Got Right, What We Got > Wrong](https://commandcenter.blogspot.com/2024/01/what-we-got-right-what-we-got-wrong.html) -`gofmt` gives Go one format. A project selects one way to write Go, and ago +`gofmt` gives Go one format. A project selects one way to write Go, and goago enforces it. Developers and coding agents follow the same rules. CI checks them. [Go at Google](https://go.dev/talks/2012/splash.article) explains why uniform code helps teams work together. @@ -31,10 +31,10 @@ Recent releases added more ways to write Go. Go 1.26 let a generic type refer to itself in its type parameter list. It also let `new` accept an expression instead of a type. Go 1.27 let methods declare type parameters. -ago does not claim that these features are bad. Each project selects one way to +goago does not claim that these features are bad. Each project selects one way to write Go. The linter enforces that choice before review. -Each ago rule removes an alternative. No rule adds syntax, rewrites code, or +Each goago rule removes an alternative. No rule adds syntax, rewrites code, or changes semantics. ## Policy boundary @@ -45,7 +45,7 @@ mistake. Its generics argument is close to the opposite position. Defining generic containers in the language without programmer access to that genericity was arguably an error. -Treat ago as a house policy, not an appeal to authority. +Treat goago as a house policy, not an appeal to authority. ## Why a linter instead of a Go fork @@ -81,14 +81,14 @@ generic functions or types in 115 non-test files. A compiler that rejects those constructs cannot build its own standard library. A useful compiler fork must restrict first-party packages while it exempts -`GOROOT` and the module cache. ago gets that scope from package loading. +`GOROOT` and the module cache. goago gets that scope from package loading. A fork also needs its own distribution and version pin through `GOTOOLCHAIN=local`. It needs a rebase every six months. A stock `gopls` can disagree with that compiler unless the project forks the language server too. Use a compiler fork when a dependency must not contain one or two constructs. -Use ago for source policy in the code that the project owns. +Use goago for source policy in the code that the project owns. ## Measured evidence diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 0000000..6d4d6f2 --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,73 @@ +# Migrate from ago to goago + +Version 0.3.0 renames the tool to `goago`, pronounced **go ago**. +The rule names, defaults, configuration schema, JSON fields, and exit statuses stay the same. + +## Go module tools + +Run these commands in each module that pins the old tool: + +```sh +go get -tool github.com/agentstation/goago/cmd/goago@v0.3.0 +go mod edit -droptool=github.com/agentstation/ago/cmd/ago +go mod tidy +go tool goago -version +go tool goago -list -format json +go tool goago -stale-ignores -format json ./... +``` + +Commit `go.mod` and `go.sum`. Change repository checks, CI commands, and agent +instructions from `go tool ago` to `go tool goago`. + +For a global installation, run: + +```sh +go install github.com/agentstation/goago/cmd/goago@v0.3.0 +``` + +## Policy and suppressions + +Rename `.ago.yml` to `.goago.yml`, or `.ago.yaml` to `.goago.yaml`. +Change `//ago:ignore` and `//ago:ignore-file` comments to `//goago:ignore` and +`//goago:ignore-file`. Keep each rule list and reason unchanged. + +The previous config filenames and comment prefixes still work during migration. +The nearest directory with a policy supplies the policy, regardless of its name. +Multiple policy files in that directory stop the run. Keep one file or select +it explicitly with `-config`. The `-init` command refuses an existing policy +under either name. New policies use `.goago.yml`. + +Use `goago.schema.json` for editor validation. Update the schema URL to +`https://raw.githubusercontent.com/agentstation/goago/main/goago.schema.json`. + +## Library and golangci-lint plugin + +Change imports from `github.com/agentstation/ago` to +`github.com/agentstation/goago`. The package name is now `goago`. +Update selectors such as `ago.Check` to `goago.Check`. + +For a custom golangci-lint build, change the plugin module and import path to +`github.com/agentstation/goago` and `github.com/agentstation/goago/plugin/golangci`. +Enable the custom linter as `goago`, then rebuild the custom binary. + +## Homebrew and agent skills + +The Homebrew cask is now `agentstation/tap/goago`. Update the tap, then run: + +```sh +brew trust --cask agentstation/tap/goago +brew install --cask agentstation/tap/goago +``` + +The Agent Skill is now `goago` in `agentstation/skills`. Replace an installed +`ago` skill with the `goago` skill. The modern Go skill uses the new command. + +## Releases and reports + +The repository is now `github.com/agentstation/goago`. GitHub redirects old +repository links. Releases through v0.2.0 contain the old module and executable. +Use v0.3.0 or later for the new module path. + +Release archives and the executable use `goago`. SARIF identifies the tool as +`goago`, and diagnostics link to the renamed repository. Historical reports +still identify the executable that produced them. diff --git a/docs/rules.md b/docs/rules.md index 047eed5..779dd07 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -91,7 +91,7 @@ kind of code. ### `no-invalid-ignore` -Every `//ago:ignore` directive must name a known rule and give a reason. A +Every `//goago:ignore` directive must name a known rule and give a reason. A directive that does not parse, names no rule, names an unknown rule, or omits its reason suppresses nothing. This rule reports that directive. @@ -156,7 +156,7 @@ fields. Enable this rule only for a project that rejects that convention. `init` runs before `main` in an order that the import graph determines. This makes an initialization failure hard to localize and test. Avoiding `init` -requires an explicit wiring convention that ago cannot provide. +requires an explicit wiring convention that goago cannot provide. ### `no-blank-import-outside-main` diff --git a/docs_test.go b/docs_test.go index 6bd1b4d..0ee98c4 100644 --- a/docs_test.go +++ b/docs_test.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "os" diff --git a/finding.go b/finding.go index 068c623..ba524af 100644 --- a/finding.go +++ b/finding.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "go/token" diff --git a/fuzz_test.go b/fuzz_test.go index e1f975c..6d00350 100644 --- a/fuzz_test.go +++ b/fuzz_test.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "bytes" @@ -34,7 +34,7 @@ func FuzzLoadConfig(f *testing.F) { func FuzzGitHubReport(f *testing.F) { f.Add("x.go", "no-goto", "goto; use a loop", 1, 1) f.Add("a.go,line=1::x", "no-goto", "::error::forged\nnext", 2, 3) - f.Add("dir/\nfile.go", "ago stale ignore", "%0A::warning::", 0, 0) + f.Add("dir/\nfile.go", "goago stale ignore", "%0A::warning::", 0, 0) f.Fuzz(func(t *testing.T, file, rule, msg string, line, col int) { r := &Report{ Findings: []Finding{{ @@ -78,11 +78,11 @@ func FuzzSkip(f *testing.F) { } func FuzzParseDirective(f *testing.F) { - f.Add("//ago:ignore no-goto -- reason") - f.Add("//ago:ignore-file * -- generated") - f.Add("//ago:ignorecase not a directive") - f.Add("//ago:ignore") - f.Add("/* ago:ignore no-goto -- no */") + f.Add("//goago:ignore no-goto -- reason") + f.Add("//goago:ignore-file * -- generated") + f.Add("//goago:ignorecase not a directive") + f.Add("//goago:ignore") + f.Add("/* goago:ignore no-goto -- no */") f.Fuzz(func(t *testing.T, text string) { if len(text) > 65536 { t.Skip() diff --git a/go.mod b/go.mod index 4757fc8..5eac512 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/agentstation/ago +module github.com/agentstation/goago go 1.25.0 diff --git a/ago.go b/goago.go similarity index 55% rename from ago.go rename to goago.go index 3ac8263..2e5624c 100644 --- a/ago.go +++ b/goago.go @@ -1,15 +1,15 @@ -// Package ago enforces one way to write Go across a codebase. +// Package goago enforces one way to write Go across a codebase. // // A project selects the Go constructs that it accepts. Developers and coding // agents use the same rule policy, and CI enforces it. // -// ago only ever rejects language constructs. It never adds syntax, never -// rewrites code, and never changes semantics. Code that passes ago is +// goago only ever rejects language constructs. It never adds syntax, never +// rewrites code, and never changes semantics. Code that passes goago is // ordinary Go that builds with the stock toolchain. // // Every rule is a [golang.org/x/tools/go/analysis.Analyzer]. Run them through -// the ago command, compose them in an analysis driver, or load the +// the goago command, compose them in an analysis driver, or load the // golangci-lint module plugin. // -// See [github.com/agentstation/ago/cmd/ago] for the command. -package ago +// See [github.com/agentstation/goago/cmd/goago] for the command. +package goago diff --git a/ago.schema.json b/goago.schema.json similarity index 87% rename from ago.schema.json rename to goago.schema.json index 6edab7b..6497487 100644 --- a/ago.schema.json +++ b/goago.schema.json @@ -1,8 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/agentstation/ago/main/ago.schema.json", - "title": "ago rule policy", - "description": "Configuration for the ago restriction-only Go linter.", + "$id": "https://raw.githubusercontent.com/agentstation/goago/main/goago.schema.json", + "title": "goago rule policy", + "description": "Configuration for the goago restriction-only Go linter.", "type": "object", "additionalProperties": false, "properties": { @@ -38,7 +38,7 @@ } }, "disable": { - "description": "Rules to disable after ago resolves enable.", + "description": "Rules to disable after goago resolves enable.", "type": "array", "uniqueItems": true, "maxItems": 4096, diff --git a/ignore.go b/ignore.go index bd1fea4..5ef09f5 100644 --- a/ignore.go +++ b/ignore.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "fmt" @@ -13,15 +13,15 @@ import ( // Directive prefixes recognised in comments. const ( - ignoreLinePrefix = "//ago:ignore" - ignoreFilePrefix = "//ago:ignore-file" + ignoreLinePrefix = "//goago:ignore" + ignoreFilePrefix = "//goago:ignore-file" // reasonSep separates the rule list from the mandatory reason. reasonSep = "--" // wildcard suppresses every rule. wildcard = "*" ) -// An ignoreDirective is one parsed //ago:ignore or //ago:ignore-file comment. +// An ignoreDirective is one parsed //goago:ignore or //goago:ignore-file comment. type ignoreDirective struct { // Pos locates the comment itself. Pos token.Pos @@ -90,12 +90,12 @@ func (ix *ignoreIndex) suppressed(fset *token.FileSet, pos token.Pos, rule strin return false } -// ignoresAnalyzer collects //ago:ignore directives. Every rule analyzer +// ignoresAnalyzer collects //goago:ignore directives. Every rule analyzer // requires it so that a rule never has to parse comments itself. var ignoresAnalyzer = &analysis.Analyzer{ - Name: "agoignores", - Doc: "collect //ago:ignore directives for the other ago analyzers", - URL: "https://github.com/agentstation/ago#fix-or-suppress-a-finding", + Name: "goagoignores", + Doc: "collect //goago:ignore directives for the other goago analyzers", + URL: "https://github.com/agentstation/goago#fix-or-suppress-a-finding", Run: runIgnores, ResultType: reflectTypeOfIgnoreIndex, } @@ -131,20 +131,25 @@ func runIgnores(pass *analysis.Pass) (any, error) { } // parseDirective turns a comment into a directive, or returns nil when the -// comment is not an ago directive at all. A malformed directive comes back +// comment is not a goago directive at all. A malformed directive comes back // with Problem set so that no-invalid-ignore can report it. func parseDirective(fset *token.FileSet, c *ast.Comment, src []byte) *ignoreDirective { text := strings.TrimRight(c.Text, " \t") - fileScoped := strings.HasPrefix(text, ignoreFilePrefix) - if !fileScoped && !strings.HasPrefix(text, ignoreLinePrefix) { - return nil + var prefix string + var fileScoped bool + // Recognize the previous name so migration preserves suppressions. + for _, candidate := range []string{ignoreFilePrefix, ignoreLinePrefix, "//ago:ignore-file", "//ago:ignore"} { + if strings.HasPrefix(text, candidate) { + prefix = candidate + fileScoped = strings.HasSuffix(candidate, "-file") + break + } } - prefix := ignoreLinePrefix - if fileScoped { - prefix = ignoreFilePrefix + if prefix == "" { + return nil } rest := text[len(prefix):] - // Require a separator so that "//ago:ignorecase" in prose is not a + // Require a separator so that "//goago:ignorecase" in prose is not a // directive. if rest != "" && !strings.HasPrefix(rest, " ") && !strings.HasPrefix(rest, "\t") { return nil @@ -177,7 +182,7 @@ func directiveProblem(prefix string, names []string, hasReason bool) string { continue } if _, ok := Lookup(name); !ok { - return fmt.Sprintf("names unknown rule %q; run \"ago -list\" for the rule set", name) + return fmt.Sprintf("names unknown rule %q; run \"goago -list\" for the rule set", name) } } if !hasReason { diff --git a/migration_test.go b/migration_test.go new file mode 100644 index 0000000..a09a566 --- /dev/null +++ b/migration_test.go @@ -0,0 +1,103 @@ +package goago + +import ( + "os" + "path/filepath" + "slices" + "strings" + "testing" +) + +func TestConfigMigration(t *testing.T) { + for _, name := range []string{".goago.yml", ".goago.yaml", ".ago.yml", ".ago.yaml"} { + t.Run(name, func(t *testing.T) { + root := t.TempDir() + path := filepath.Join(root, name) + if err := os.WriteFile(path, []byte("enable: [no-goto]\ntests: true\nexclude: [generated]\n"), 0o600); err != nil { + t.Fatal(err) + } + sub := filepath.Join(root, "nested") + if err := os.Mkdir(sub, 0o700); err != nil { + t.Fatal(err) + } + cfg, err := LoadConfig(sub, "") + if err != nil { + t.Fatal(err) + } + if cfg.Path() != path || !cfg.Tests || !cfg.Skip("generated/x.go") || !slices.Equal(ruleNames(cfg.Enabled(nil)), []string{"no-goto"}) { + t.Fatalf("policy changed during migration: %+v", cfg) + } + }) + } +} + +func TestConfigMigrationRejectsCompetingNames(t *testing.T) { + for _, old := range []string{".ago.yml", ".ago.yaml"} { + for _, current := range []string{".goago.yml", ".goago.yaml"} { + t.Run(old+current, func(t *testing.T) { + root := t.TempDir() + for _, name := range []string{old, current} { + if err := os.WriteFile(filepath.Join(root, name), []byte("enable: [no-goto]\n"), 0o600); err != nil { + t.Fatal(err) + } + } + if _, err := LoadConfig(root, ""); err == nil || !strings.Contains(err.Error(), "multiple policy files") { + t.Fatalf("error = %v, want competing policy error", err) + } + if _, err := LoadConfig(root, filepath.Join(root, current)); err != nil { + t.Fatalf("explicit config: %v", err) + } + }) + } + } +} + +func TestDirectiveMigration(t *testing.T) { + for _, name := range []string{"ago", "goago"} { + t.Run(name, func(t *testing.T) { + root := t.TempDir() + files := map[string]string{ + "go.mod": "module example.com/migration\n\ngo 1.25\n", + "line.go": `package migration +func good() { +//NAME:ignore no-goto -- state transition +goto done +done: +return +} +func invalid() { +//NAME:ignore no-goto +goto done +done: +return +} +//NAME:ignore no-goto -- stale example +func stale() {} +//NAME:ignorecase is ordinary prose +func prose() {} +`, + "file.go": `//NAME:ignore-file no-goto -- file exception +package migration +func file() { goto done; done: return } +`, + } + for path, source := range files { + if err := os.WriteFile(filepath.Join(root, path), []byte(strings.ReplaceAll(source, "NAME", name)), 0o600); err != nil { + t.Fatal(err) + } + } + gotoRule, _ := Lookup("no-goto") + invalidRule, _ := Lookup("no-invalid-ignore") + r, err := Check(Options{Dir: root, Rules: []Rule{gotoRule, invalidRule}, ReportStaleIgnores: true}) + if err != nil { + t.Fatal(err) + } + if len(r.Errors) != 0 || len(r.Findings) != 2 || len(r.StaleIgnores) != 1 { + t.Fatalf("unexpected report: %+v", r) + } + if r.Findings[0].Rule != "no-invalid-ignore" || r.Findings[1].Rule != "no-goto" || r.Findings[1].Line != 10 { + t.Fatalf("unexpected findings: %+v", r.Findings) + } + }) + } +} diff --git a/pass.go b/pass.go index 0635098..3e067f1 100644 --- a/pass.go +++ b/pass.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "go/ast" @@ -17,7 +17,7 @@ func newAnalyzer(name, doc string, run func(*checkPass)) *analysis.Analyzer { a := &analysis.Analyzer{ Name: identName(name), Doc: doc, - URL: "https://github.com/agentstation/ago/blob/main/docs/rules.md#" + name, + URL: "https://github.com/agentstation/goago/blob/main/docs/rules.md#" + name, Requires: []*analysis.Analyzer{ignoresAnalyzer}, } a.Run = func(pass *analysis.Pass) (any, error) { @@ -29,7 +29,7 @@ func newAnalyzer(name, doc string, run func(*checkPass)) *analysis.Analyzer { } // A checkPass is the analysis pass a rule sees. It narrows [analysis.Pass] to -// the reporting path that honours //ago:ignore, so that a rule cannot report +// the reporting path that honours //goago:ignore, so that a rule cannot report // around suppression by accident. type checkPass struct { *analysis.Pass @@ -37,7 +37,7 @@ type checkPass struct { ignores *ignoreIndex } -// reportf records a violation at n unless an //ago:ignore directive covers +// reportf records a violation at n unless an //goago:ignore directive covers // that line for this rule. func (c *checkPass) reportf(n ast.Node, format string, args ...any) { if c.ignores.suppressed(c.Fset, n.Pos(), c.rule) { diff --git a/plugin/golangci/plugin.go b/plugin/golangci/plugin.go index 4589da0..eabef95 100644 --- a/plugin/golangci/plugin.go +++ b/plugin/golangci/plugin.go @@ -1,23 +1,23 @@ -// Package golangci registers ago as a golangci-lint module plugin. +// Package golangci registers goago as a golangci-lint module plugin. // // Build a custom golangci-lint binary that includes it with a .custom-gcl.yml: // // version: v2.6.0 // plugins: -// - module: github.com/agentstation/ago -// import: github.com/agentstation/ago/plugin/golangci +// - module: github.com/agentstation/goago +// import: github.com/agentstation/goago/plugin/golangci // version: latest // -// Then run "golangci-lint custom" and enable the "ago" linter in +// Then run "golangci-lint custom" and enable the "goago" linter in // .golangci.yml. Settings select the rule set. Without them the plugin runs -// ago's default rules. +// goago's default rules. // // linters: // enable: -// - ago +// - goago // settings: // custom: -// ago: +// goago: // type: module // settings: // enable: [no-goto, no-naked-return] @@ -30,16 +30,16 @@ import ( "github.com/golangci/plugin-module-register/register" "golang.org/x/tools/go/analysis" - "github.com/agentstation/ago" + "github.com/agentstation/goago" ) func init() { - register.Plugin("ago", New) + register.Plugin("goago", New) } // Settings selects which rules the plugin runs. It mirrors the enable and -// disable keys of .ago.yml, including the "default" and "all" meta-names. -// Leaving both empty runs ago's default rule set. +// disable keys of .goago.yml, including the "default" and "all" meta-names. +// Leaving both empty runs goago's default rule set. type Settings struct { Enable []string `json:"enable"` Disable []string `json:"disable"` @@ -57,9 +57,9 @@ func New(conf any) (register.LinterPlugin, error) { return nil, err } - cfg := &ago.Config{Enable: settings.Enable, Disable: settings.Disable} + cfg := &goago.Config{Enable: settings.Enable, Disable: settings.Disable} if err := cfg.Validate(); err != nil { - return nil, fmt.Errorf("ago: %w", err) + return nil, fmt.Errorf("goago: %w", err) } rules := cfg.Enabled(nil) @@ -75,7 +75,7 @@ func (p *Plugin) BuildAnalyzers() ([]*analysis.Analyzer, error) { return p.analyzers, nil } -// GetLoadMode reports that ago's rules need full type information. +// GetLoadMode reports that goago's rules need full type information. func (p *Plugin) GetLoadMode() string { return register.LoadModeTypesInfo } diff --git a/plugin/golangci/plugin_test.go b/plugin/golangci/plugin_test.go index fc6d057..09ffcd7 100644 --- a/plugin/golangci/plugin_test.go +++ b/plugin/golangci/plugin_test.go @@ -3,7 +3,7 @@ package golangci import ( "testing" - "github.com/agentstation/ago" + "github.com/agentstation/goago" "github.com/golangci/plugin-module-register/register" ) @@ -17,7 +17,7 @@ func TestNew(t *testing.T) { { name: "no settings runs the default rule set", conf: map[string]any{}, - want: ago.DefaultNames(), + want: goago.DefaultNames(), }, { name: "explicit enable selects exactly those rules", @@ -27,12 +27,12 @@ func TestNew(t *testing.T) { { name: "disable subtracts from the default set", conf: map[string]any{"enable": []string{"default"}, "disable": []string{"no-goto"}}, - want: without(ago.DefaultNames(), "no-goto"), + want: without(goago.DefaultNames(), "no-goto"), }, { name: "all selects every rule", conf: map[string]any{"enable": []string{"all"}}, - want: ago.Names(), + want: goago.Names(), }, { name: "an unknown rule is an error, not a silent no-op", @@ -69,7 +69,7 @@ func TestNew(t *testing.T) { } got := make([]string, len(analyzers)) for i, a := range analyzers { - rule, ok := ago.Lookup(a.Name) + rule, ok := goago.Lookup(a.Name) if !ok { t.Fatalf("analyzer %q is not a known rule", a.Name) } diff --git a/report.go b/report.go index 8da4ca6..e6f0638 100644 --- a/report.go +++ b/report.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "encoding/json" @@ -43,19 +43,19 @@ func ParseFormat(s string) (Format, error) { return "", fmt.Errorf("unknown format %q; want one of %s", s, strings.Join(names, ", ")) } -// A Report is everything one ago run produced. The JSON encoding of this type -// is ago's machine-readable contract. Fields grow over time but existing +// A Report is everything one goago run produced. The JSON encoding of this type +// is goago's machine-readable contract. Fields grow over time but existing // fields keep their name and meaning. type Report struct { // SchemaVersion is the JSON report schema version. SchemaVersion int `json:"schemaVersion"` - // Version is the ago version that produced the report. + // Version is the goago version that produced the report. Version string `json:"version"` // Rules lists the canonical names of the rules that ran, sorted. Rules []string `json:"rules"` // Findings holds every violation, ordered by file, line, and column. Findings []Finding `json:"findings"` - // StaleIgnores lists //ago:ignore directives that suppressed nothing. + // StaleIgnores lists //goago:ignore directives that suppressed nothing. StaleIgnores []StaleIgnore `json:"staleIgnores"` // Errors holds load or parse failures. A non-empty Errors means the run // did not finish and Findings may omit violations. @@ -105,7 +105,7 @@ func (r *Report) writeText(w io.Writer) error { } } for _, s := range r.StaleIgnores { - if _, err := fmt.Fprintf(w, "%s: //ago:ignore suppressed nothing (%s)\n", + if _, err := fmt.Fprintf(w, "%s: //goago:ignore suppressed nothing (%s)\n", s.Position(), strings.Join(s.Rules, ",")); err != nil { return err } @@ -130,7 +130,7 @@ func (r *Report) writeGitHub(w io.Writer) error { level, escapeWorkflowParam(f.File), f.Line, f.Column, f.EndLine, f.EndColumn, - escapeWorkflowParam("ago "+f.Rule), + escapeWorkflowParam("goago "+f.Rule), escapeWorkflowData(f.Message)); err != nil { return err } @@ -139,8 +139,8 @@ func (r *Report) writeGitHub(w io.Writer) error { if _, err := fmt.Fprintf(w, "::warning file=%s,line=%d,col=%d,title=%s::%s\n", escapeWorkflowParam(s.File), s.Line, s.Column, - escapeWorkflowParam("ago stale ignore"), - escapeWorkflowData("//ago:ignore suppressed nothing: "+strings.Join(s.Rules, ","))); err != nil { + escapeWorkflowParam("goago stale ignore"), + escapeWorkflowData("//goago:ignore suppressed nothing: "+strings.Join(s.Rules, ","))); err != nil { return err } } diff --git a/report_test.go b/report_test.go index 8f7e9c1..a066a46 100644 --- a/report_test.go +++ b/report_test.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "bytes" @@ -87,7 +87,7 @@ func TestGitHubFormatOneLinePerFinding(t *testing.T) { t.Fatal(err) } got := buf.String() - want := "::error file=x.go,line=1,col=1,endLine=0,endColumn=0,title=ago no-goto::ok\n" + want := "::error file=x.go,line=1,col=1,endLine=0,endColumn=0,title=goago no-goto::ok\n" if got != want { t.Fatalf("got %q\nwant %q", got, want) } diff --git a/rule.go b/rule.go index f2fd5b2..b399b07 100644 --- a/rule.go +++ b/rule.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "fmt" @@ -8,7 +8,7 @@ import ( "golang.org/x/tools/go/analysis" ) -// Severity classifies how strongly ago objects to a construct. Every rule +// Severity classifies how strongly goago objects to a construct. Every rule // currently reports at [Error]. The field exists so that report formats with // a severity axis, such as SARIF, carry an honest value rather than a // hardcoded one. @@ -24,20 +24,20 @@ const ( // A Rule is one restriction, paired with the analyzer that enforces it. // -// Name is the canonical kebab-case name used by the ago command, by .ago.yml, -// and by //ago:ignore directives. The Analyzer.Name field is the same name +// Name is the canonical kebab-case name used by the goago command, by .goago.yml, +// and by //goago:ignore directives. The Analyzer.Name field is the same name // with the hyphens removed, because go/analysis requires analyzer names to be -// valid Go identifiers. The ago command accepts either spelling. +// valid Go identifiers. The goago command accepts either spelling. type Rule struct { // Name is the canonical kebab-case rule name, such as "no-goto". Name string - // Summary is a single line shown by "ago -list". + // Summary is a single line shown by "goago -list". Summary string // Rationale explains why the rule exists and when to turn it off. It is // the analyzer's Doc body and the text an agent reads to decide whether a // violation is worth fixing or worth ignoring. Rationale string - // Default reports whether ago enables the rule when no configuration + // Default reports whether goago enables the rule when no configuration // selects a rule set explicitly. Default bool // Reverts names the Go release that introduced the construct this rule @@ -51,7 +51,7 @@ type Rule struct { // DocURL returns the rule-reference anchor that documents the rule. func (r Rule) DocURL() string { - return "https://github.com/agentstation/ago/blob/main/docs/rules.md#" + r.Name + return "https://github.com/agentstation/goago/blob/main/docs/rules.md#" + r.Name } // registry holds every rule in registration order. @@ -59,12 +59,12 @@ var registry []Rule // register adds a rule to the registry and wires up its analyzer metadata. // Each rule file calls it at package level. It panics on a duplicate name -// because that is a programming error in ago itself, not a condition a caller +// because that is a programming error in goago itself, not a condition a caller // can handle. func register(r Rule) Rule { for _, existing := range registry { if existing.Name == r.Name { - panic(fmt.Sprintf("ago: duplicate rule %q", r.Name)) + panic(fmt.Sprintf("goago: duplicate rule %q", r.Name)) } } registry = append(registry, r) @@ -77,7 +77,7 @@ func identName(name string) string { return strings.ReplaceAll(name, "-", "") } -// Rules returns every rule ago knows about, in registration order. +// Rules returns every rule goago knows about, in registration order. func Rules() []Rule { out := make([]Rule, len(registry)) copy(out, registry) diff --git a/rule_constraints.go b/rule_constraints.go index 8b9a71f..734552d 100644 --- a/rule_constraints.go +++ b/rule_constraints.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "go/ast" diff --git a/rule_declarations.go b/rule_declarations.go index f9112b7..ac9ab67 100644 --- a/rule_declarations.go +++ b/rule_declarations.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "go/ast" diff --git a/rule_generics.go b/rule_generics.go index 3c778fd..527d01f 100644 --- a/rule_generics.go +++ b/rule_generics.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "go/ast" @@ -46,7 +46,7 @@ var RuleNoGenericDecls = register(Rule{ Reverts: "1.18", Default: false, Severity: Error, - Rationale: `Reverts generics entirely. This is the strictest rule ago offers. It is the + Rationale: `Reverts generics entirely. This is the strictest rule goago offers. It is the least likely to be right for a given codebase: it forbids type parameters on any func or type declaration. diff --git a/rule_ignore.go b/rule_ignore.go index 7cac054..ce2753f 100644 --- a/rule_ignore.go +++ b/rule_ignore.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "go/ast" @@ -8,7 +8,7 @@ import ( // RuleNoInvalidIgnore keeps suppression directives honest. var RuleNoInvalidIgnore = register(Rule{ Name: "no-invalid-ignore", - Summary: "every //ago:ignore must name a known rule and give a reason", + Summary: "every //goago:ignore must name a known rule and give a reason", Default: true, Severity: Error, Rationale: `A suppression that names no rule silences everything on the line, and a @@ -17,16 +17,16 @@ quietly, which is how a lint configuration rots. This rule requires the full form: - //ago:ignore no-goto -- hand-written state machine, see docs/parser.md + //goago:ignore no-goto -- hand-written state machine, see docs/parser.md -The reason is not decoration. It is the only record of why ago granted the +The reason is not decoration. It is the only record of why goago granted the exception. A reviewer or a coding agent reads it before deciding whether the exception still applies. Turning this rule off is possible but self-defeating. It is the rule that makes every other rule's escape hatch auditable.`, Analyzer: newAnalyzer("no-invalid-ignore", - "reject //ago:ignore directives that name no known rule or give no reason", + "reject //goago:ignore directives that name no known rule or give no reason", checkInvalidIgnore), }) @@ -36,7 +36,7 @@ func checkInvalidIgnore(c *checkPass) { } for _, d := range c.ignores.all { if d.Problem != "" { - c.reportf(directivePos{d.Pos}, "//ago:ignore %s", d.Problem) + c.reportf(directivePos{d.Pos}, "//goago:ignore %s", d.Problem) } } } diff --git a/rule_imports.go b/rule_imports.go index a4beed3..9a4505b 100644 --- a/rule_imports.go +++ b/rule_imports.go @@ -1,4 +1,4 @@ -package ago +package goago import "go/ast" diff --git a/rule_newexpr.go b/rule_newexpr.go index 441b42e..05b7b55 100644 --- a/rule_newexpr.go +++ b/rule_newexpr.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "go/ast" diff --git a/rules_test.go b/rules_test.go index a7e6f46..b4d114b 100644 --- a/rules_test.go +++ b/rules_test.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "go/parser" @@ -106,7 +106,7 @@ func (t *Typed[E]) Value() E { return t.v } // TestAnalyzersValidate checks the invariants go/analysis itself imposes, so // that a golangci-lint plugin or a go vet -vettool build cannot fail on -// metadata ago controls. +// metadata goago controls. func TestAnalyzersValidate(t *testing.T) { if err := analysis.Validate(append(Analyzers(), ignoresAnalyzer)); err != nil { t.Fatal(err) diff --git a/sarif.go b/sarif.go index a50d491..33119cb 100644 --- a/sarif.go +++ b/sarif.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "encoding/json" @@ -7,7 +7,7 @@ import ( ) // SARIF 2.1.0 is the interchange format GitHub code scanning ingests. This -// file models only the subset of the schema ago populates. The omitempty tags +// file models only the subset of the schema goago populates. The omitempty tags // keep the document to what a static analysis tool without fixes needs to emit. type sarifLog struct { Schema string `json:"$schema"` @@ -129,9 +129,9 @@ func (r *Report) writeSARIF(w io.Writer) error { Version: "2.1.0", Runs: []sarifRun{{ Tool: sarifTool{Driver: sarifDriver{ - Name: "ago", + Name: "goago", Version: Version, - InformationURI: "https://github.com/agentstation/ago", + InformationURI: "https://github.com/agentstation/goago", Rules: declared, }}, Results: results, diff --git a/sprintf.go b/sprintf.go index 8820d34..c7f4550 100644 --- a/sprintf.go +++ b/sprintf.go @@ -1,4 +1,4 @@ -package ago +package goago import "fmt" diff --git a/testdata/modules/ignores/ignores.go b/testdata/modules/ignores/ignores.go index 4eba491..ec9e9aa 100644 --- a/testdata/modules/ignores/ignores.go +++ b/testdata/modules/ignores/ignores.go @@ -2,45 +2,45 @@ package ignores // A well-formed directive is not reported and suppresses its rule. func Good() { - //ago:ignore no-goto -- a reason that explains the exception + //goago:ignore no-goto -- a reason that explains the exception goto end end: } // A directive with no reason suppresses nothing and is itself reported. func NoReason() { - //ago:ignore no-goto + //goago:ignore no-goto goto end end: } // A directive that names no rule. func NoRule() { - //ago:ignore + //goago:ignore goto end end: } // A directive naming a rule that does not exist. func UnknownRule() { - //ago:ignore no-gotoo -- misspelled rule name + //goago:ignore no-gotoo -- misspelled rule name goto end end: } // A directive whose reason is empty after the separator. func EmptyReason() { - //ago:ignore no-goto -- + //goago:ignore no-goto -- goto end end: } // Prose that merely starts with the prefix is not a directive. // -//ago:ignorecase is not a directive +//goago:ignorecase is not a directive func Prose() {} // A well-formed directive that suppresses nothing is stale. // -//ago:ignore no-dot-import -- nothing here dot-imports anything +//goago:ignore no-dot-import -- nothing here dot-imports anything func Stale() {} diff --git a/testdata/src/nogoto/file_scoped.go b/testdata/src/nogoto/file_scoped.go index 571f394..ae2ade8 100644 --- a/testdata/src/nogoto/file_scoped.go +++ b/testdata/src/nogoto/file_scoped.go @@ -1,4 +1,4 @@ -//ago:ignore-file no-goto -- this whole file is a generated state machine +//goago:ignore-file no-goto -- this whole file is a generated state machine package nogoto diff --git a/testdata/src/nogoto/nogoto.go b/testdata/src/nogoto/nogoto.go index 7ef7781..b75cb4b 100644 --- a/testdata/src/nogoto/nogoto.go +++ b/testdata/src/nogoto/nogoto.go @@ -7,26 +7,26 @@ end: // A suppressed goto, in the own-line form. func Suppressed() { - //ago:ignore no-goto -- hand-written state machine + //goago:ignore no-goto -- hand-written state machine goto end end: } // A suppressed goto, in the trailing form. func SuppressedTrailing() { - goto end //ago:ignore no-goto -- trailing directive + goto end //goago:ignore no-goto -- trailing directive end: } // The wildcard suppresses every rule on the line. func SuppressedWildcard() { - goto end //ago:ignore * -- suppress everything here + goto end //goago:ignore * -- suppress everything here end: } // A directive with no reason suppresses nothing. func NotSuppressed() { - //ago:ignore no-goto + //goago:ignore no-goto goto end // want `goto end` end: } diff --git a/version.go b/version.go index 73e2595..c298267 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ -package ago +package goago import "runtime/debug" @@ -16,7 +16,7 @@ func resolveVersion(linked string, info *debug.BuildInfo) string { if linked != "" && linked != "dev" { return linked } - if info != nil && info.Main.Path == "github.com/agentstation/ago" && + if info != nil && info.Main.Path == "github.com/agentstation/goago" && info.Main.Version != "" && info.Main.Version != "(devel)" { return info.Main.Version } diff --git a/version_test.go b/version_test.go index bf7c5e6..4e0d40d 100644 --- a/version_test.go +++ b/version_test.go @@ -1,4 +1,4 @@ -package ago +package goago import ( "runtime/debug" @@ -17,7 +17,7 @@ func TestResolveVersion(t *testing.T) { name: "module version", linked: "dev", info: &debug.BuildInfo{Main: debug.Module{ - Path: "github.com/agentstation/ago", + Path: "github.com/agentstation/goago", Version: "v0.2.0", }}, want: "v0.2.0", @@ -26,7 +26,7 @@ func TestResolveVersion(t *testing.T) { name: "local source", linked: "dev", info: &debug.BuildInfo{Main: debug.Module{ - Path: "github.com/agentstation/ago", + Path: "github.com/agentstation/goago", Version: "(devel)", }}, want: "dev",