3 Commits

Author SHA1 Message Date
f1189a00e3 Enable depguard so a non-test file cannot import test support
All checks were successful
check / check (push) Successful in 8s
Test-support code exists to hand a test privileges the program itself
must never have, so it must not be compiled into the shipped binary.
depguard now states that rule: in files that are neither tests nor
inside a package whose directory name ends in test, the denied imports
are refused.

depguard matches an import path against a list of prefixes, so it
cannot be told "any path whose last segment ends in test". A repo's own
test-support packages therefore have to be named one at a time under
deny, by full import path, and the policy paragraph now says that list
is the one part of the vendored copy a repo may extend.

Requested by sneak on
sneak/homoicon#1029 to replace a
per-repository check written as a Go test.

Model: opus-5
2026-09-06 01:23:30 +00:00
f77d785bed Scope the .golangci.yml agent prohibition to vendored copies (#49)
All checks were successful
check / check (push) Successful in 9s
SPECULATIVE and ahead of your ruling — nothing here is urgent and closing it costs nothing. One sentence of policy prose changed; no config file is touched.

## The contradiction

`REPO_POLICIES.md` line 266 currently reads:

> `.golangci.yml` is standardized and must _NEVER_ be modified by an agent, only manually by the user.

Stated unqualified, that forbids an agent from modifying `.golangci.yml` **anywhere** — including the canonical copy in this repo, which is the only place it can ever be fixed. An agent that wants to remediate a linter problem must either violate the rule or leave the problem standing. A rule that cannot be complied with and satisfied at the same time gets resolved ad hoc, differently by each reader, which is the worst of both outcomes it was trying to produce.

This is not hypothetical. It has already cost real time:

- The `gomodguard` deprecation (#25) has been open since 2026-08-07 and still prints on every lint run in every consuming Go repo.
- One agent read the rule as binding here and **declined to open even a speculative branch**, so the fix was not written at all on that pass.
- #47 exists only because a later request was explicit enough to override the reading, and its lead comment asks for exactly this ruling before the PR itself can be judged on its merits.
- The same warning is refiled downstream as sneak/homoicon#4, where it is correctly marked owner-only and correctly punted upstream.

Each new agent that meets the rule reruns this whole argument.

## The change

Scope the prohibition to the vendored copy, and name the one legitimate path by which the config can change:

```
- `.golangci.yml` is standardized. The vendored copy in a consuming repo must
  _NEVER_ be modified by an agent: fetch it from
  `https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml` and keep it
  byte-identical, so that no repo can quietly loosen its own linting. Linter
  configuration changes are made to the canonical copy in the `prompts` repo and
  reach consuming repos by re-vendoring; an agent may open a PR against
  canonical, which only the user merges.
```

The version pin sentence that followed is unchanged.

This keeps the property the rule exists for — no repo silently weakens its own linting, and divergence from canonical stays detectable — while removing the reading that freezes canonical itself. Your control is not reduced: an agent may open a PR here, and only you merge it.

## Scope of the wording sweep

I grepped every `.md` in the repo for the absolute phrasing. It appears in **exactly one place**, `prompts/REPO_POLICIES.md` lines 266-267.

`EXISTING_REPO_CHECKLIST.md` (line 39) and `NEW_REPO_CHECKLIST.md` (line 63) both mention `.golangci.yml`, but only as "fetch from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml`" — an instruction to vendor canonical verbatim, which is exactly what the scoped rule says. Neither carries a prohibition, so neither needs changing and neither is left contradicting the other. `REPO_POLICIES.md` line 414 lists `.golangci.yml` as a required file, also unaffected.

Note that the repo-root `REPO_POLICIES.md` is a symlink to `prompts/REPO_POLICIES.md`, so the single edit covers both paths.

## Deliberately NOT included

This PR does **not** change `.golangci.yml`. The `gomodguard` fix stays in #47 so the two can be judged separately — the policy question is worth settling on its own terms regardless of what you decide about that config change, and merging them would collapse two decisions into one.

## Unrelated observation, for the record

While verifying #47 against a scratch `sneak/homoicon` clone, I found that homoicon's vendored `.golangci.yml` is sha256 `391ea68e637432980f1db0776076f51578fa58193bbd17f4b40ad725975e21f8`, while canonical `main` is `021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb`. The whole difference is a three-line comment recording a one-time agent edit you authorized on 2026-08-07; the config is functionally identical.

That matters only if you ever want a hash-based drift guard against canonical, which #25 floats as an offline alternative to `golangci-lint config verify`: such a guard would already report homoicon as drifted on day one. Worth knowing before building one. No change proposed here.

## Validation

`make check` passes (`prettier --check '**/*.md' --tab-width 4 --prose-wrap always`: all matched files clean). `make fmt` produced no further changes. `last_modified` in the front matter updated to 2026-08-19 per this file's own rule.

Co-authored-by: sneak <sneak@sneak.berlin>
Co-authored-by: Jeffrey Paul <sneak@noreply.example.org>
Reviewed-on: #49
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-08-30 06:26:19 +02:00
3f8201d532 Require thin cmd/ entrypoints: all logic in internal/ or pkg/ (#54)
All checks were successful
check / check (push) Successful in 13s
Codifies your ruling (2026-08-30, filed as homoicon issue 555): no project logic outside `internal/` or `pkg/`; each `cmd/<name>/` is a single `main.go` whose body is one call into library code.

- `CODE_STYLEGUIDE_GO.md`: strengthens the "keep `main` small" rule to the single-call form and adds the no-logic-outside-`internal/`-or-`pkg/` rule.
- `REPO_POLICIES.md`: annotates `cmd/` in the canonical subdirectory list accordingly. (Root copy is a symlink; one edit covers both.)

Co-authored-by: sneak <sneak@sneak.berlin>
Reviewed-on: #54
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-08-30 04:20:42 +02:00
3 changed files with 53 additions and 21 deletions

View File

@@ -13,7 +13,6 @@ linters:
disable:
# Genuinely incompatible with project patterns
- exhaustruct # Requires all struct fields
- depguard # Dependency allow/block lists
- godot # Requires comments to end with periods
- wsl # Deprecated, replaced by wsl_v5
- wrapcheck # Too verbose for internal packages
@@ -28,6 +27,32 @@ linters:
max-complexity: 15
dupl:
threshold: 100
depguard:
# Test-support code must not be compiled into the shipped binary. A
# test-support package exists to hand a test privileges the program
# itself must never have, so a file that is not a test must not import
# one. Test files, and the files inside a package whose directory name
# ends in `test`, are where that code belongs, and are exempt.
#
# The deny list below is the one part of this file a repository is
# expected to extend, and the only part it may. depguard matches an
# import path against a list of prefixes, so it cannot be told "any path
# whose last segment ends in test"; a repository's own test-support
# packages have to be named here one at a time, by full import path,
# under a module path that differs from repository to repository. Add
# them; change nothing else.
rules:
test-support:
list-mode: lax
files:
- "$all"
- "!$test"
- "!**/*test/**"
deny:
- pkg: net/http/httptest
desc: >-
Test-support code belongs in test files and in packages whose
directory name ends in test, not in the shipped binary.
issues:
max-issues-per-linter: 0

View File

@@ -124,10 +124,15 @@ last_modified: 2026-03-18
1. Keep the `main()` function as small as possible.
1. Keep the `main` package as small as possible. Move as much code as is
feasible to a library package, even if it's an internal one. `main` is just
an entrypoint to your code, not a place for implementations. Exception:
single-file scripts.
1. Keep the `main` package as small as possible. Each `cmd/<name>/` directory
contains a single `main.go` whose body is one call into library code (for
example `os.Exit(cli.Main())` calling `internal/cli`). All CLI logic — flag
parsing, subcommand dispatch, argument handling, output formatting — lives
in `internal/` or `pkg/`, not in `cmd/`. `main` is just an entrypoint to
your code, not a place for implementations. Exception: single-file scripts.
1. No project logic outside `internal/` or `pkg/`. Anything in `cmd/` is a thin
entrypoint only.
1. HTTP HandleFuncs should be returned from methods or functions that need to
handle HTTP requests. Don't use methods or your top level functions as

View File

@@ -1,6 +1,6 @@
---
title: Repository Policies
last_modified: 2026-08-20
last_modified: 2026-08-19
---
This document covers repository structure, tooling, and workflow standards. Code
@@ -69,15 +69,8 @@ style conventions are in separate documents:
outputs the project's name. Scripts that need the name call
`script/projectname` — e.g. `script/docker` assembles its image tag from it —
so those scripts stay byte-identical across all repos. Repo-type-specific
verifications (e.g. `go mod tidy` verification in Go repos) belong in
`script/check`, which is the gate the other entrypoints funnel through: it
runs in the Dockerfile build step and therefore in CI, and `script/precommit`
inherits it by calling `script/check`. In `script/precommit` alone such a
check binds only the contributors who installed the hook, and the hook file
itself stays a shim that carries no checks of its own. Anything added to
`script/check` must be read-only, since `make check` must not modify files, so
use a verify or diff mode (`go mod tidy -diff`) rather than a command that
rewrites the tree. Model scripts are at
pre-commit extras (e.g. `go mod tidy` verification in Go repos) belong in
`script/precommit`, not in the hook itself. Model scripts are at
`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`. The README
must document the provided scripts in an **Entrypoints** section (see the
README requirements below).
@@ -270,11 +263,18 @@ style conventions are in separate documents:
- Make all changes on a feature branch. You can do whatever you want on a
feature branch.
- `.golangci.yml` is standardized and must _NEVER_ be modified by an agent, only
manually by the user. Fetch from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml`. The
canonical golangci-lint version is v2.12.2 (released 2026-05-06), installed
commit-pinned via
- `.golangci.yml` is standardized. The vendored copy in a consuming repo must
_NEVER_ be modified by an agent: fetch it from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml` and keep it
byte-identical, so that no repo can quietly loosen its own linting. Linter
configuration changes are made to the canonical copy in the `prompts` repo and
reach consuming repos by re-vendoring; an agent may open a PR against
canonical, which only the user merges. One list is exempt from byte-identity,
because it cannot be written once for every repo: the `deny` list of the
`test-support` depguard rule, where a repo names its own test-support packages
by full import path. A repo adds entries there and changes nothing else, and a
re-vendor carries its entries forward. The canonical golangci-lint version is
v2.12.2 (released 2026-05-06), installed commit-pinned via
`go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9cf3faa61a4e378e879ece580256d76e5`.
- When pinning images or packages by hash, add a comment above the reference
@@ -394,7 +394,9 @@ style conventions are in separate documents:
language-specific config). Everything else goes in a subdirectory. Canonical
subdirectory names:
- `bin/` — executable scripts and tools
- `cmd/` — Go command entrypoints
- `cmd/` — Go command entrypoints; thin only: one `main.go` per binary whose
body is a single call into `internal/` or `pkg/`, no project logic in
`cmd/`
- `configs/` — configuration templates and examples
- `deploy/` — deployment manifests (k8s, compose, terraform)
- `docs/` — documentation and markdown (README.md stays in root)