Two unpushed tooling commits since 68195cd: golangci-lint config and a Makefile with build-time version injection. No origin remote — review scoped to 68195cd..HEAD plus working-tree fixes applied during this review.
Adds Makefile and .golangci.yml so 'how do I build / lint / test this?' has one answer.
main.go exposes Version/BuildTime/GitCommit package vars wired to -X main.X=... ldflags.
Three fixes applied during review: lazy LDFLAGS shells, removed brittle golangci-lint version-parse, removed redundant command -v guards, added -o pulsar to build.
Ready to push
All quality and efficiency findings were fixed in the working tree. make check (fmt/vet/lint/test) and make build both pass. The remaining tooling — a custom go-functions grep, package-level version vars, and the test-flag matrix — were left as-is per the simplicity guidance.
a98ddca [chore]: add golangci-lint config and inject ldflags on install 5be4b56 [chore]: add Makefile and build-time version vars working-tree Pre-push review fixes This branch adds two new files and a tiny tweak to main.go so that the project has standard build and quality-check tooling. A Makefile exposes one-word commands like make build and make test; a .golangci.yml tells the linter which checks to run; three new variables in main.go let the version, build time, and git commit be stamped into the binary at compile time.
Without this, every developer has to remember the right go build flags. With it, "how do I build this?" has one answer.
Three additions to the toolchain: Makefile wraps the standard Go targets (build, test, lint, vet, check, install, coverage, benchmarks); .golangci.yml selects a v2-schema linter set (govet, staticcheck, revive, gocritic, goconst, ineffassign, misspell, unconvert) with interface{}→any rewrite; main.go exposes Version/BuildTime/GitCommit package vars consumed by -X main.X=... ldflags.
BUILD_TIME/GIT_COMMIT switched from := to = so make fmt/help don't shell out to git and date.lint target dropped its version-parse and command -v guard — .golangci.yml's declared version: "2" already fails loudly on a v1 binary.security-scan dropped its which gosec check — gosec's own missing-binary error is clear enough.build now emits -o pulsar explicitly, matching build-release.The lazy LDFLAGS chain works because Make evaluates =-assigned variables on reference, not declaration. LDFLAGS is only referenced inside build, build-release, and install recipes, so non-build targets never invoke git rev-parse or date. Verified with make -n fmt.
Removing the version-parse gate trades a custom error message for golangci-lint's own startup failure on schema mismatch. CI behaviour is unchanged; local developer feedback path is shorter by ~50–100 ms per make lint. The version vars in main are package-scoped and only read by newRootCmd(); no need for a separate version/ package until external consumers appear.
go build . directly, version vars stay at zero values — that's fine, but document it before users open bug reports about empty --version output.main.go
Why it matters. Adds package-level mutable strings that ldflags overwrite at link time. Reviewers should confirm they're only read by `newRootCmd()` and not exported.
What to look at. main.go:11-22
Makefile
Why it matters. Switching from `:=` to `=` defers the `git rev-parse` and `date` shells until LDFLAGS is actually referenced. Non-build targets (`fmt`, `help`, `vet`, `test`) now skip those calls.
What to look at. Makefile:5-13
Makefile
Why it matters. Removed a ~12-line shell block that pre-checked binary presence and parsed `golangci-lint version` output with regex. `.golangci.yml` declares `version: "2"` which fails loudly on the wrong binary.
What to look at. Makefile:67-71
Makefile
Why it matters. Makes `build` and `build-release` consistent — both now produce a binary explicitly named `pulsar` regardless of working directory or module-name quirks.
What to look at. Makefile:15-16
The original block parsed golangci-lint version stdout with grep -oE + cut to enforce v2. .golangci.yml's version: "2" declaration already causes a v1 binary to fail at startup with a clear schema error — duplicating that check in Make adds brittleness and latency for no gain. CI runners and developers get the same error from the upstream tool.
Make's := evaluates the right-hand side at parse time, so every make help shelled out to git rev-parse. Switching to = defers evaluation until LDFLAGS is referenced — which only happens in build, build-release, and install. Trade-off: = re-evaluates on every reference, but each of those targets references LDFLAGS exactly once.
Three near-duplicate targets (test, test-verbose, test-integration, test-integration-verbose) were flagged as copy-paste. Collapsing into a TEST_FLAGS variable was considered and skipped — the matrix is short, each line is self-documenting, and a flag variable would obscure what make test-verbose actually runs. Three similar lines is better than a premature abstraction.
| Severity | Area | Finding | Resolution |
|---|---|---|---|
| minor | Makefile:6-7 (LDFLAGS eager eval) | BUILD_TIME and GIT_COMMIT used `:=`, shelling out to `git` and `date` on every Make invocation, even for unrelated targets. | Switched to `=` for deferred evaluation. Verified with `make -n fmt` — no shells expanded. |
| minor | Makefile:75 (brittle version parse) | `golangci-lint version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 | cut -d. -f1` — fragile pipeline depending on human-readable output format. | Removed the entire version-check block; rely on `.golangci.yml`'s `version: "2"` to fail loudly. |
| nit | Makefile:70 + Makefile:107 (redundant existence checks) | `command -v golangci-lint` and `which gosec` guards add latency before invocations that would themselves error clearly when the tool is missing. | Removed both checks. gosec install instructions moved to a comment above the target. |
| nit | Makefile:16 (inconsistent -o flag) | `build` target omitted `-o pulsar` while `build-release` set it explicitly — relies on `go build` defaulting to module name. | Added `-o pulsar` to `build` for consistency. |
| info | Makefile:27-44 (test-flag matrix) | Four near-duplicate test targets vary only in flags. Could collapse into a `TEST_FLAGS`-based parameterisation. | Left as-is — the matrix is short, each target is self-documenting, and a parameter variable would obscure what each command does. |
Click to expand.
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..14cda23
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,40 @@
+version: "2"
+
+run:
+ timeout: 5m
+ tests: false
+
+linters:
+ enable:
+ - govet
+ - ineffassign
+ - misspell
+ - staticcheck
+ - revive
+ - goconst
+ - gocritic
+ - unconvert
+ disable:
+ - errcheck # Fluent API methods intentionally discard return values
+ - unused # Some methods may be used via reflection/interfaces
+ settings:
+ revive:
+ rules:
+ - name: exported
+ disabled: false
+ arguments:
+ - "disableStutteringCheck"
+
+formatters:
+ enable:
+ - gofmt
+ settings:
+ gofmt:
+ simplify: false
+ rewrite-rules:
+ - pattern: interface{}
+ replacement: any
+
+issues:
+ max-issues-per-linter: 0
+ max-same-issues: 0
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..31423a2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,147 @@
+# Pulsar Makefile
+# Provides standard targets for common development tasks
+
+# Version information. BUILD_TIME and GIT_COMMIT are deferred so that targets
+# which never reference LDFLAGS (help, fmt, vet, test, ...) skip the shell calls.
+VERSION ?= dev
+BUILD_TIME = $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
+GIT_COMMIT = $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
+
+# Build flags for version injection (vars are no-ops until declared in main)
+LDFLAGS = -X main.Version=$(VERSION) \
+ -X main.BuildTime=$(BUILD_TIME) \
+ -X main.GitCommit=$(GIT_COMMIT)
+
+# Build the Pulsar application
+build:
+ go build -ldflags "$(LDFLAGS)" -o pulsar .
+
+# Build the Pulsar application with version information
+build-release:
+ @if [ "$(VERSION)" = "dev" ]; then \
+ echo "Error: VERSION must be set for release builds. Usage: make build-release VERSION=1.2.3"; \
+ exit 1; \
+ fi
+ go build -ldflags "$(LDFLAGS)" -o pulsar .
+
+# Run all tests
+test:
+ go test ./...
+
+# Run tests with verbose output and coverage
+test-verbose:
+ go test -v -cover ./...
+
+# Run only integration tests
+test-integration:
+ INTEGRATION=1 go test ./...
+
+# Run integration tests with verbose output
+test-integration-verbose:
+ INTEGRATION=1 go test -v ./...
+
+# Run both unit and integration tests
+test-all: test test-integration
+
+# Generate test coverage report
+test-coverage:
+ go test -coverprofile=coverage.out ./...
+ go tool cover -html=coverage.out -o coverage.html
+ @echo "Coverage report generated at coverage.html"
+
+# Run benchmark tests
+benchmarks:
+ go test -bench=. ./...
+
+# Run benchmarks with memory profiling
+benchmarks-mem:
+ go test -bench=. -benchmem ./...
+
+# Format Go code
+fmt:
+ go fmt ./...
+
+# Run go vet for static analysis
+vet:
+ go vet ./...
+
+# Run linter. .golangci.yml declares version: "2" — golangci-lint itself
+# fails loudly if a v1 binary is on PATH or if the binary is missing.
+lint:
+ golangci-lint run
+
+# Run full validation suite
+check: fmt vet lint test
+
+# Clean build artifacts
+clean:
+ rm -f pulsar
+ rm -rf dist/
+ rm -f coverage.out coverage.html
+
+# Install the application
+install:
+ go install -ldflags "$(LDFLAGS)" .
+
+# Clean up go.mod and go.sum
+deps-tidy:
+ go mod tidy
+
+# Update dependencies to latest versions
+deps-update:
+ go get -u ./...
+ go mod tidy
+
+# Run security scan. Requires gosec on PATH:
+# go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest
+security-scan:
+ gosec ./...
+
+# List all Go functions in the project
+go-functions:
+ @echo "Finding all functions in the project..."
+ @grep -r "^func " . --include="*.go" | grep -v vendor/
+
+# Show help
+help:
+ @echo "Available targets:"
+ @echo ""
+ @echo "Build targets:"
+ @echo " build - Build the Pulsar application with version info"
+ @echo " build-release - Build release version (requires VERSION=x.y.z)"
+ @echo " install - Install the application"
+ @echo " clean - Clean build artifacts and coverage files"
+ @echo ""
+ @echo "Testing targets:"
+ @echo " test - Run Go unit tests"
+ @echo " test-verbose - Run tests with verbose output and coverage"
+ @echo " test-integration - Run only integration tests"
+ @echo " test-integration-verbose - Run integration tests with verbose output"
+ @echo " test-all - Run both unit and integration tests"
+ @echo " test-coverage - Generate test coverage report (HTML)"
+ @echo ""
+ @echo "Benchmark targets:"
+ @echo " benchmarks - Run benchmark tests"
+ @echo " benchmarks-mem - Run benchmarks with memory profiling"
+ @echo ""
+ @echo "Code quality targets:"
+ @echo " fmt - Format Go code"
+ @echo " vet - Run go vet for static analysis"
+ @echo " lint - Run linter (requires golangci-lint v2+)"
+ @echo " check - Run full validation suite (fmt, vet, lint, test)"
+ @echo " security-scan - Run security analysis (requires gosec)"
+ @echo ""
+ @echo "Dependency management:"
+ @echo " deps-tidy - Clean up go.mod and go.sum"
+ @echo " deps-update - Update dependencies to latest versions"
+ @echo ""
+ @echo "Development utilities:"
+ @echo " go-functions - List all Go functions in the project"
+ @echo " help - Show this help message"
+ @echo ""
+ @echo "Build examples:"
+ @echo " make build - Build with dev version"
+ @echo " make build VERSION=1.2.3 - Build with specific version"
+ @echo " make build-release VERSION=1.2.3 - Build release version"
+
+.PHONY: build build-release test test-verbose test-integration test-integration-verbose test-all test-coverage benchmarks benchmarks-mem fmt vet lint check clean install deps-tidy deps-update security-scan go-functions help
diff --git a/main.go b/main.go
index 8979d42..093e967 100644
--- a/main.go
+++ b/main.go
@@ -7,11 +7,19 @@ import (
"github.com/spf13/cobra"
)
+// Build-time variables injected via -ldflags "-X main.Var=value".
+var (
+ Version = "dev"
+ BuildTime = "unknown"
+ GitCommit = "unknown"
+)
+
func newRootCmd() *cobra.Command {
root := &cobra.Command{
Use: "pulsar",
Short: "Code review feed server",
Long: "Pulsar turns a directory of locally-generated code review HTML files into an RSS feed served over Tailscale.",
+ Version: fmt.Sprintf("%s (commit %s, built %s)", Version, GitCommit, BuildTime),
SilenceUsage: true,
SilenceErrors: true,
}
If a user runs go build . directly (bypassing the Makefile), Version/BuildTime/GitCommit stay as empty strings. pulsar --version will print empties. Acceptable for local dev builds, but worth mentioning in the README if not already covered.