@bfirestone45/opencode-slop-review 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,401 @@
1
+ # Go AI Slop Signals
2
+
3
+ Language-specific signals for Go codebases. These supplement the universal signals
4
+ in SKILL.md -- apply both.
5
+
6
+ ## What idiomatic Go looks like
7
+
8
+ ### At version 1.26+
9
+ - `log/slog` for structured logging (stdlib since 1.21)
10
+ - Range-over-func iterators (since 1.23)
11
+ - Generic `slices`/`maps` functions over hand-rolled loops
12
+ - `cmp.Or` for default values
13
+ - Structured concurrency patterns with `errgroup`
14
+ - `maps.Clone`, `slices.Concat`, `slices.Contains` etc.
15
+ - New `http.ServeMux` with method-based routing (1.22+)
16
+
17
+ ### Stdlib preferences
18
+ - Logging: `log/slog` over `log` or third-party loggers in new code
19
+ - Slices: `slices` package over manual loops for search/sort/compare
20
+ - Maps: `maps` package for clone/keys/values operations
21
+ - HTTP: `http.NewServeMux` patterns with method routing (1.22+)
22
+ - Testing: `testing.TB` helpers, `t.Cleanup` over deferred cleanup
23
+
24
+ ### Error handling convention
25
+ - Always wrap errors with `fmt.Errorf("context: %w", err)`
26
+ - Use `errors.Is`/`errors.As` for comparison, never string matching
27
+ - Return errors to caller, do not `log.Fatal` in library or handler code
28
+ - Sentinel errors for expected conditions, wrapped errors for unexpected
29
+
30
+ ### Project adaptation
31
+ Before flagging any idiom violation, check if the project's idiom baseline uses a different convention. The baseline overrides these defaults.
32
+
33
+ ---
34
+
35
+ ## Priority order for Go
36
+
37
+ 1. Error handling -- this is where AI Go code fails most visibly
38
+ 2. Context propagation -- threading context.Context correctly
39
+ 3. Concurrency -- goroutine lifecycle, channel ownership, sync primitives
40
+ 4. Interface and type design -- idiomatic Go vs. Java-in-Go
41
+ 5. Testing -- table-driven tests, test helpers, subtests
42
+
43
+ ---
44
+
45
+ ## Error handling (highest priority)
46
+
47
+ This is the single biggest tell in AI-generated Go. Humans who write Go daily internalize
48
+ error handling patterns; AI frequently gets them subtly wrong.
49
+
50
+ **Fatal/print instead of return:**
51
+ ```go
52
+ // SLOP -- log.Fatal in a library or handler function
53
+ if err != nil {
54
+ log.Fatal(err)
55
+ }
56
+
57
+ // SLOP -- fmt.Println for errors
58
+ if err != nil {
59
+ fmt.Println("error:", err)
60
+ }
61
+
62
+ // IDIOMATIC -- return the error to the caller
63
+ if err != nil {
64
+ return fmt.Errorf("fetching user %d: %w", id, err)
65
+ }
66
+ ```
67
+
68
+ **Missing error wrapping:**
69
+ ```go
70
+ // SLOP -- no context, breaks error unwrapping
71
+ if err != nil {
72
+ return err
73
+ }
74
+
75
+ // SLOP -- wraps but without %w, breaks errors.Is/As
76
+ if err != nil {
77
+ return fmt.Errorf("failed to connect: %v", err)
78
+ }
79
+
80
+ // IDIOMATIC
81
+ if err != nil {
82
+ return fmt.Errorf("connecting to %s: %w", addr, err)
83
+ }
84
+ ```
85
+
86
+ **String comparison on errors:**
87
+ ```go
88
+ // SLOP
89
+ if err.Error() == "not found" {
90
+ // ...
91
+ }
92
+
93
+ // IDIOMATIC
94
+ if errors.Is(err, ErrNotFound) {
95
+ // ...
96
+ }
97
+ ```
98
+
99
+ **Swallowed errors:**
100
+ ```go
101
+ // SLOP -- error silently discarded
102
+ result, _ := doSomething()
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Context propagation
108
+
109
+ **Missing context.Context in signatures:**
110
+ ```go
111
+ // SLOP -- no context parameter
112
+ func FetchUser(id int) (*User, error) {
113
+
114
+ // IDIOMATIC -- context is first parameter
115
+ func FetchUser(ctx context.Context, id int) (*User, error) {
116
+ ```
117
+
118
+ **context.Background() mid-callstack:**
119
+ ```go
120
+ // SLOP -- creates a new root context deep in the call chain
121
+ func (s *Service) Process(data []byte) error {
122
+ ctx := context.Background() // should come from caller
123
+ return s.store.Save(ctx, data)
124
+ }
125
+ ```
126
+
127
+ **No cancellation handling:**
128
+ ```go
129
+ // SLOP -- ignores context cancellation
130
+ func worker(ctx context.Context) {
131
+ for {
132
+ doWork()
133
+ time.Sleep(time.Second)
134
+ }
135
+ }
136
+
137
+ // IDIOMATIC
138
+ func worker(ctx context.Context) {
139
+ for {
140
+ select {
141
+ case <-ctx.Done():
142
+ return
143
+ default:
144
+ doWork()
145
+ }
146
+ select {
147
+ case <-ctx.Done():
148
+ return
149
+ case <-time.After(time.Second):
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Concurrency
158
+
159
+ **Goroutine leaks -- no cancellation path:**
160
+ ```go
161
+ // SLOP -- goroutine runs forever, no way to stop it
162
+ go func() {
163
+ for {
164
+ process(queue)
165
+ }
166
+ }()
167
+
168
+ // IDIOMATIC -- cancellable via context
169
+ go func() {
170
+ for {
171
+ select {
172
+ case <-ctx.Done():
173
+ return
174
+ case item := <-queue:
175
+ process(item)
176
+ }
177
+ }
178
+ }()
179
+ ```
180
+
181
+ **Channel ownership confusion:**
182
+ - Channel created by one goroutine, closed by another (race condition risk)
183
+ - Channel created but never closed (goroutines blocked on range forever)
184
+ - Unbuffered channel used where buffered is needed (deadlock risk)
185
+
186
+ **sync.Mutex overuse:**
187
+ ```go
188
+ // SLOP -- mutex protecting a counter that should use atomic
189
+ var mu sync.Mutex
190
+ var count int
191
+
192
+ // BETTER
193
+ var count atomic.Int64
194
+ ```
195
+
196
+ **sync.Mutex where single-goroutine ownership works:**
197
+ If a struct is only accessed from one goroutine, it does not need a mutex.
198
+ AI often adds mutexes "just in case" -- a strong slop signal.
199
+
200
+ ---
201
+
202
+ ## Type and interface misuse
203
+
204
+ **Interface defined in the wrong package:**
205
+ ```go
206
+ // SLOP -- interface defined next to its implementation
207
+ // (Go convention: interfaces belong in the consuming package)
208
+ type UserStore interface {
209
+ GetUser(ctx context.Context, id int) (*User, error)
210
+ }
211
+
212
+ type userStore struct { ... }
213
+ func (s *userStore) GetUser(...) { ... }
214
+ ```
215
+
216
+ **Premature interface:**
217
+ ```go
218
+ // SLOP -- interface with exactly one implementation and one consumer
219
+ type Processor interface {
220
+ Process(data []byte) error
221
+ }
222
+ ```
223
+
224
+ **`interface{}` instead of `any`, or `any` instead of generics:**
225
+ ```go
226
+ // SLOP -- interface{} is an alias for any since 1.18; using the old syntax is dated
227
+ func Contains(slice []interface{}, item interface{}) bool {
228
+
229
+ // STILL SLOP at 1.26+ -- hand-rolled contains; use slices.Contains
230
+ func Contains[T comparable](slice []T, item T) bool {
231
+
232
+ // IDIOMATIC at 1.26+ -- use the stdlib slices package
233
+ import "slices"
234
+ found := slices.Contains(slice, item)
235
+ ```
236
+
237
+ **Value receiver on mutating method:**
238
+ ```go
239
+ // SLOP -- s is a copy, mutation is lost
240
+ func (s Service) SetTimeout(d time.Duration) {
241
+ s.timeout = d
242
+ }
243
+
244
+ // CORRECT
245
+ func (s *Service) SetTimeout(d time.Duration) {
246
+ s.timeout = d
247
+ }
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Other Go idioms
253
+
254
+ **defer inside a loop:**
255
+ ```go
256
+ // SLOP -- defers accumulate until function return, not loop iteration
257
+ for _, f := range files {
258
+ fd, _ := os.Open(f)
259
+ defer fd.Close() // all closes happen at function exit
260
+ }
261
+
262
+ // CORRECT -- extract to helper or close explicitly
263
+ for _, f := range files {
264
+ if err := processFile(f); err != nil {
265
+ return err
266
+ }
267
+ }
268
+ ```
269
+
270
+ **Hand-rolled slice/map operations (1.26+):**
271
+ ```go
272
+ // SLOP -- manual contains check
273
+ found := false
274
+ for _, v := range items {
275
+ if v == target {
276
+ found = true
277
+ break
278
+ }
279
+ }
280
+
281
+ // IDIOMATIC at 1.26+ -- use slices package
282
+ found := slices.Contains(items, target)
283
+
284
+ // SLOP -- manual sort
285
+ sort.Slice(items, func(i, j int) bool { return items[i] < items[j] })
286
+
287
+ // IDIOMATIC at 1.26+
288
+ slices.Sort(items)
289
+
290
+ // SLOP -- manual map key collection
291
+ keys := make([]string, 0, len(m))
292
+ for k := range m {
293
+ keys = append(keys, k)
294
+ }
295
+
296
+ // IDIOMATIC at 1.26+
297
+ keys := slices.Collect(maps.Keys(m))
298
+ ```
299
+
300
+ **Old-style logging instead of slog (1.26+):**
301
+ ```go
302
+ // SLOP in new code -- unstructured logging
303
+ log.Printf("user %d logged in from %s", id, ip)
304
+
305
+ // IDIOMATIC at 1.26+ -- structured logging with log/slog
306
+ slog.Info("user logged in", "user_id", id, "ip", ip)
307
+ ```
308
+
309
+ **Default value chains without cmp.Or (1.26+):**
310
+ ```go
311
+ // SLOP -- verbose conditional default
312
+ port := os.Getenv("PORT")
313
+ if port == "" {
314
+ port = "8080"
315
+ }
316
+
317
+ // IDIOMATIC at 1.26+
318
+ port := cmp.Or(os.Getenv("PORT"), "8080")
319
+ ```
320
+
321
+ **HTTP server without timeouts:**
322
+ ```go
323
+ // SLOP
324
+ http.ListenAndServe(":8080", handler)
325
+
326
+ // IDIOMATIC at 1.26+ -- use method-based routing on ServeMux
327
+ mux := http.NewServeMux()
328
+ mux.HandleFunc("GET /users/{id}", getUser)
329
+ mux.HandleFunc("POST /users", createUser)
330
+
331
+ srv := &http.Server{
332
+ Addr: ":8080",
333
+ Handler: mux,
334
+ ReadTimeout: 15 * time.Second,
335
+ WriteTimeout: 15 * time.Second,
336
+ IdleTimeout: 60 * time.Second,
337
+ }
338
+ srv.ListenAndServe()
339
+ ```
340
+
341
+ **Global singletons via init():**
342
+ ```go
343
+ // SLOP
344
+ var db *sql.DB
345
+
346
+ func init() {
347
+ var err error
348
+ db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
349
+ if err != nil {
350
+ log.Fatal(err)
351
+ }
352
+ }
353
+
354
+ // BETTER -- dependency injection, initialized in main()
355
+ ```
356
+
357
+ ---
358
+
359
+ ## Security signals
360
+
361
+ - SQL via `fmt.Sprintf` instead of parameterized queries (`db.Query(query, args...)`)
362
+ - `http.ListenAndServe` with no TLS and no reverse proxy documented
363
+ - `os/exec.Command` with user input concatenated into the command string
364
+ - `http.Get` / `http.Post` with no timeout (uses `http.DefaultClient` which has no timeout)
365
+
366
+ ---
367
+
368
+ ## Go-specific test signals
369
+
370
+ **Not using table-driven tests** -- the canonical Go testing pattern:
371
+ ```go
372
+ // SLOP -- separate test functions for each case
373
+ func TestParseValid(t *testing.T) { ... }
374
+ func TestParseEmpty(t *testing.T) { ... }
375
+ func TestParseInvalid(t *testing.T) { ... }
376
+
377
+ // IDIOMATIC
378
+ func TestParse(t *testing.T) {
379
+ tests := []struct {
380
+ name string
381
+ input string
382
+ want Result
383
+ wantErr bool
384
+ }{
385
+ {"valid", "good", Result{...}, false},
386
+ {"empty", "", Result{}, true},
387
+ {"invalid", "bad", Result{}, true},
388
+ }
389
+ for _, tt := range tests {
390
+ t.Run(tt.name, func(t *testing.T) {
391
+ got, err := Parse(tt.input)
392
+ // ...
393
+ })
394
+ }
395
+ }
396
+ ```
397
+
398
+ - `t.Log` or `fmt.Println` instead of assertion -- test always passes
399
+ - No `t.Helper()` on test helper functions (error locations point to helper, not caller)
400
+ - No `t.Parallel()` on tests that are safe to parallelize
401
+ - `testify` used in a codebase that uses stdlib `testing` everywhere else (or vice versa)
@@ -0,0 +1,263 @@
1
+ # Python AI Slop Signals
2
+
3
+ Language-specific signals for Python codebases. These supplement the universal signals
4
+ in SKILL.md -- apply both.
5
+
6
+ ## What idiomatic Python looks like
7
+
8
+ ### At version 3.13+
9
+
10
+ - Union types: `X | None` not `Optional[X]`, `X | Y` not `Union[X, Y]`
11
+ - Built-in generics: `list[str]` not `typing.List[str]`, `dict[str, int]` not `typing.Dict[str, int]`
12
+ - `@deprecated` decorator from `warnings`
13
+ - `StrEnum` for string enumerations (stdlib since 3.11)
14
+ - `tomllib` for TOML parsing (stdlib since 3.11)
15
+ - `ExceptionGroup` and `except*` for concurrent error handling
16
+ - Walrus operator `:=` where it reduces duplication
17
+ - Per-interpreter GIL / free-threaded mode awareness
18
+
19
+ ### Stdlib preferences
20
+
21
+ - Filesystem: `pathlib.Path` over `os.path` in new code
22
+ - Caching: `functools.cache` (3.9+) or `lru_cache` over hand-rolled memoization
23
+ - Data containers: `dataclasses` or `NamedTuple` over plain dicts for structured data
24
+ - Iteration: `itertools` (product, chain, groupby) over nested manual loops
25
+ - Context managers: `contextlib.contextmanager` over manual `__enter__`/`__exit__`
26
+ - Temporary files: `tempfile` with context managers, never hardcoded paths
27
+
28
+ ### Error handling convention
29
+
30
+ - Specific exceptions over bare `except Exception`
31
+ - `raise X from e` to preserve tracebacks
32
+ - EAFP for duck typing, LBYL for everything else
33
+ - `.get()` over try/except KeyError for dict access
34
+
35
+ ### Project adaptation
36
+
37
+ Before flagging any idiom violation, check if the project's idiom baseline (from Step 0)
38
+ uses a different convention. The baseline overrides these defaults.
39
+
40
+ ---
41
+
42
+ ## Priority order for Python
43
+
44
+ 1. Error handling -- bare excepts, swallowed errors, exceptions for control flow
45
+ 2. Idiomatic Python -- are they writing Python or Java-in-Python?
46
+ 3. Type system usage -- modern typing vs. no types vs. over-typed
47
+ 4. Async correctness -- if async is used, is it used properly?
48
+ 5. Security -- eval, exec, shell injection, pickle
49
+
50
+ ---
51
+
52
+ ## Error handling
53
+
54
+ **Bare exception swallowing** -- the single most common AI Python tell:
55
+ ```python
56
+ # SLOP: silent failure
57
+ try:
58
+ result = do_thing()
59
+ except Exception:
60
+ pass
61
+
62
+ # SLOP: catching too broadly then re-raising generically
63
+ try:
64
+ data = parse(raw)
65
+ except Exception as e:
66
+ raise ValueError(f"Parse failed: {e}") # loses traceback, catches too much
67
+
68
+ # BETTER: specific exceptions, context preserved
69
+ try:
70
+ data = parse(raw)
71
+ except json.JSONDecodeError as e:
72
+ raise ParseError(f"Invalid JSON at position {e.pos}") from e
73
+ ```
74
+
75
+ **Exceptions for control flow** -- using try/except where a conditional or `.get()` works:
76
+ ```python
77
+ # SLOP
78
+ try:
79
+ value = data["key"]
80
+ except KeyError:
81
+ value = default
82
+
83
+ # IDIOMATIC
84
+ value = data.get("key", default)
85
+ ```
86
+
87
+ **Over-defensive error handling** -- wrapping operations that cannot fail:
88
+ ```python
89
+ # SLOP: len() on a list cannot raise
90
+ try:
91
+ count = len(items)
92
+ except Exception:
93
+ count = 0
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Classic footguns AI still produces
99
+
100
+ **Mutable default arguments:**
101
+ ```python
102
+ # SLOP -- the list is shared across all calls
103
+ def append_to(item, target=[]):
104
+ target.append(item)
105
+ return target
106
+
107
+ # CORRECT
108
+ def append_to(item, target=None):
109
+ if target is None:
110
+ target = []
111
+ target.append(item)
112
+ return target
113
+ ```
114
+
115
+ **Global state as a crutch:**
116
+ ```python
117
+ # SLOP
118
+ _cache = {}
119
+ def get_user(user_id):
120
+ global _cache
121
+ if user_id not in _cache:
122
+ _cache[user_id] = fetch(user_id)
123
+ return _cache[user_id]
124
+
125
+ # BETTER: encapsulate state, or use functools.lru_cache
126
+ ```
127
+
128
+ **Resources without context managers:**
129
+ ```python
130
+ # SLOP
131
+ f = open("data.csv")
132
+ data = f.read()
133
+ f.close()
134
+
135
+ # IDIOMATIC
136
+ with open("data.csv") as f:
137
+ data = f.read()
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Type and style signals
143
+
144
+ **Missing or outdated type hints** -- with 3.13+ as the minimum floor, these are all
145
+ non-idiomatic and should be flagged:
146
+ - No type hints at all on function signatures
147
+ - `Optional[X]` instead of `X | None` -- the `Optional` alias is legacy
148
+ - `typing.List`, `typing.Dict`, `typing.Tuple`, `typing.Set` instead of `list`, `dict`, `tuple`, `set` -- built-in generics have been available since 3.9
149
+ - `typing.Union[X, Y]` instead of `X | Y` -- the pipe syntax has been available since 3.10
150
+ - Importing from `typing` for constructs that now live in the stdlib (e.g., `typing.NamedTuple` when `typing` import is the only reason)
151
+
152
+ **isinstance chains instead of structural typing:**
153
+ ```python
154
+ # SLOP
155
+ def process(item):
156
+ if isinstance(item, str):
157
+ return handle_str(item)
158
+ elif isinstance(item, int):
159
+ return handle_int(item)
160
+ elif isinstance(item, list):
161
+ return handle_list(item)
162
+
163
+ # BETTER: Protocol, singledispatch, or rethink the interface
164
+ ```
165
+
166
+ **Avoiding the stdlib:**
167
+ - `os.path.join` instead of `pathlib.Path` in new code
168
+ - Manual iteration instead of `itertools` (product, chain, groupby)
169
+ - Hand-rolled memoization instead of `functools.lru_cache` or `cache`
170
+ - Manual context managers instead of `contextlib.contextmanager`
171
+ - Custom data containers instead of `dataclasses` or `NamedTuple`
172
+ - `collections.OrderedDict` in Python 3.7+ (regular dicts are ordered)
173
+
174
+ **`*args, **kwargs` on internal functions:**
175
+ ```python
176
+ # SLOP -- lazy interface design, impossible to type-check
177
+ def create_user(*args, **kwargs):
178
+ return User(*args, **kwargs)
179
+
180
+ # BETTER: explicit parameters with types
181
+ def create_user(name: str, email: str, role: Role = Role.USER) -> User:
182
+ return User(name=name, email=email, role=role)
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Async signals
188
+
189
+ **Blocking calls in async code** -- the most insidious AI async mistake:
190
+ ```python
191
+ # SLOP -- requests blocks the event loop
192
+ async def fetch_data(url):
193
+ response = requests.get(url) # BLOCKS
194
+ return response.json()
195
+
196
+ # SLOP -- time.sleep blocks the event loop
197
+ async def poll():
198
+ while True:
199
+ await check()
200
+ time.sleep(5) # BLOCKS -- should be await asyncio.sleep(5)
201
+ ```
202
+
203
+ **Deprecated async patterns:**
204
+ ```python
205
+ # SLOP (deprecated since 3.10)
206
+ loop = asyncio.get_event_loop()
207
+ loop.run_until_complete(main())
208
+
209
+ # IDIOMATIC
210
+ asyncio.run(main())
211
+ ```
212
+
213
+ **Missing await** -- code runs but the coroutine never executes:
214
+ ```python
215
+ # SLOP -- result is a coroutine object, not the actual result
216
+ async def process():
217
+ result = fetch_data() # missing await
218
+ return result
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Security signals
224
+
225
+ These are serious -- flag as Critical regardless of other context:
226
+
227
+ - `eval()` or `exec()` on any external or user-influenced input
228
+ - Shell commands with `shell=True` and string formatting for the command
229
+ - `pickle.loads()` / `pickle.load()` on untrusted data
230
+ - Secrets in default argument values, module-level constants, or committed `.env` files
231
+ - `yaml.load()` without `Loader=SafeLoader` (allows arbitrary code execution)
232
+ - SQL built via f-strings or `.format()` instead of parameterized queries
233
+ - `hashlib.md5()` or `hashlib.sha1()` for security purposes (use sha256+)
234
+ - `random` module for security-sensitive values (use `secrets` module)
235
+
236
+ ---
237
+
238
+ ## Python-specific test signals
239
+
240
+ - `unittest.TestCase` subclasses in a project that uses `pytest` everywhere else
241
+ - `mock.patch` on everything -- tests that mock the entire world test nothing
242
+ - No `parametrize` / `params` where the function has obvious input variations
243
+ - `assert result is not None` as the only assertion (proves nothing about correctness)
244
+ - Test files that import and call the function but do not assert meaningful behavior
245
+ - `conftest.py` with fixtures that do too much setup (hiding test complexity)
246
+ - No `tmp_path` or `tmp_path_factory` -- tests writing to fixed filesystem paths
247
+
248
+ ---
249
+
250
+ ## Framework-specific notes
251
+
252
+ Be aware that some frameworks have conventions that look unusual:
253
+
254
+ - **Dagster:** `@asset` decorators, `@op`, resource injection via type annotations,
255
+ `MaterializeResult` returns. These are framework conventions, not slop.
256
+ - **Django:** Class-based views, `Meta` inner classes, `models.Manager` subclasses.
257
+ - **FastAPI:** `Depends()` injection, Pydantic models for validation, `async def` route
258
+ handlers that may legitimately use sync ORM calls via `run_in_executor`.
259
+ - **SQLAlchemy:** `Column()`, `relationship()`, declarative base patterns.
260
+ - **Pydantic:** `model_validator`, `field_validator`, `ConfigDict` -- these are idiomatic.
261
+
262
+ Do not flag framework-conventional patterns. Do flag when framework patterns are mixed
263
+ incorrectly (e.g., raw SQL in a project that uses SQLAlchemy ORM everywhere else).