@mrciphersmith/keryx 0.2.164 → 0.3.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.
Files changed (100) hide show
  1. package/dist/cli.js +85355 -56749
  2. package/dist/core.js +28605 -18901
  3. package/package.json +2 -2
  4. package/src/gdgraph/affected-report.ts +141 -0
  5. package/src/gdgraph/build.ts +170 -23
  6. package/src/gdgraph/service.ts +6 -0
  7. package/src/gdgraph/staleness.ts +253 -45
  8. package/src/gdskills/bundled/agents/codebase-navigator.md +55 -0
  9. package/src/gdskills/bundled/agents/design-advisor.md +64 -0
  10. package/src/gdskills/bundled/agents/docs-maintainer.md +56 -0
  11. package/src/gdskills/bundled/agents/end-to-end-tester.md +56 -0
  12. package/src/gdskills/bundled/agents/error-path-auditor.md +57 -0
  13. package/src/gdskills/bundled/agents/go-build-fixer.md +52 -0
  14. package/src/gdskills/bundled/agents/go-code-auditor.md +49 -0
  15. package/src/gdskills/bundled/agents/performance-auditor.md +63 -0
  16. package/src/gdskills/bundled/agents/python-build-fixer.md +52 -0
  17. package/src/gdskills/bundled/agents/python-code-auditor.md +49 -0
  18. package/src/gdskills/bundled/agents/refactoring-steward.md +61 -0
  19. package/src/gdskills/bundled/agents/security-auditor.md +62 -0
  20. package/src/gdskills/bundled/agents/test-first-driver.md +61 -0
  21. package/src/gdskills/bundled/agents/work-planner.md +62 -0
  22. package/src/gdskills/bundled/install-manifest.json +530 -0
  23. package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +29 -1
  24. package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +2 -2
  25. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +1 -1
  26. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +74 -246
  27. package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +19 -0
  28. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +10 -0
  29. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +5 -0
  30. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-backend.md +50 -0
  31. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-frontend.md +52 -0
  32. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/review-report.md +143 -0
  33. package/src/gdskills/bundled/stacks/go/agent-refs.json +3 -0
  34. package/src/gdskills/bundled/stacks/go/governance/eval.json +1745 -0
  35. package/src/gdskills/bundled/stacks/go/governance/scout.json +31 -0
  36. package/src/gdskills/bundled/stacks/go/pack.json +41 -0
  37. package/src/gdskills/bundled/stacks/go/rules/coding-style.mdc +85 -0
  38. package/src/gdskills/bundled/stacks/go/rules/patterns.mdc +65 -0
  39. package/src/gdskills/bundled/stacks/go/rules/security.mdc +73 -0
  40. package/src/gdskills/bundled/stacks/go/rules/testing.mdc +68 -0
  41. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/SKILL.md +138 -0
  42. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/evals.json +75 -0
  43. package/src/gdskills/bundled/stacks/go/skills/go-code-review/SKILL.md +121 -0
  44. package/src/gdskills/bundled/stacks/go/skills/go-code-review/evals.json +72 -0
  45. package/src/gdskills/bundled/stacks/go/skills/go-implementation/SKILL.md +122 -0
  46. package/src/gdskills/bundled/stacks/go/skills/go-implementation/evals.json +76 -0
  47. package/src/gdskills/bundled/stacks/go/skills/go-testing/SKILL.md +126 -0
  48. package/src/gdskills/bundled/stacks/go/skills/go-testing/evals.json +73 -0
  49. package/src/gdskills/bundled/stacks/python/agent-refs.json +3 -0
  50. package/src/gdskills/bundled/stacks/python/governance/eval.json +1758 -0
  51. package/src/gdskills/bundled/stacks/python/governance/scout.json +34 -0
  52. package/src/gdskills/bundled/stacks/python/pack.json +41 -0
  53. package/src/gdskills/bundled/stacks/python/rules/coding-style.mdc +63 -0
  54. package/src/gdskills/bundled/stacks/python/rules/patterns.mdc +88 -0
  55. package/src/gdskills/bundled/stacks/python/rules/security.mdc +84 -0
  56. package/src/gdskills/bundled/stacks/python/rules/testing.mdc +77 -0
  57. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/SKILL.md +144 -0
  58. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/evals.json +74 -0
  59. package/src/gdskills/bundled/stacks/python/skills/python-code-review/SKILL.md +155 -0
  60. package/src/gdskills/bundled/stacks/python/skills/python-code-review/evals.json +72 -0
  61. package/src/gdskills/bundled/stacks/python/skills/python-implementation/SKILL.md +143 -0
  62. package/src/gdskills/bundled/stacks/python/skills/python-implementation/evals.json +78 -0
  63. package/src/gdskills/bundled/stacks/python/skills/python-testing/SKILL.md +132 -0
  64. package/src/gdskills/bundled/stacks/python/skills/python-testing/evals.json +73 -0
  65. package/src/gdskills/bundled/stacks/react/agent-refs.json +4 -0
  66. package/src/gdskills/bundled/stacks/react/governance/eval.json +2188 -0
  67. package/src/gdskills/bundled/stacks/react/governance/scout.json +40 -0
  68. package/src/gdskills/bundled/stacks/react/pack.json +42 -0
  69. package/src/gdskills/bundled/stacks/react/rules/coding-style.mdc +58 -0
  70. package/src/gdskills/bundled/stacks/react/rules/patterns.mdc +79 -0
  71. package/src/gdskills/bundled/stacks/react/rules/security.mdc +70 -0
  72. package/src/gdskills/bundled/stacks/react/rules/testing.mdc +60 -0
  73. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/SKILL.md +139 -0
  74. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/evals.json +72 -0
  75. package/src/gdskills/bundled/stacks/react/skills/react-code-review/SKILL.md +148 -0
  76. package/src/gdskills/bundled/stacks/react/skills/react-code-review/evals.json +74 -0
  77. package/src/gdskills/bundled/stacks/react/skills/react-implementation/SKILL.md +140 -0
  78. package/src/gdskills/bundled/stacks/react/skills/react-implementation/evals.json +74 -0
  79. package/src/gdskills/bundled/stacks/react/skills/react-testing/SKILL.md +142 -0
  80. package/src/gdskills/bundled/stacks/react/skills/react-testing/evals.json +83 -0
  81. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/SKILL.md +155 -0
  82. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/evals.json +74 -0
  83. package/src/gdskills/bundled/stacks/ts-js-node/agent-refs.json +4 -0
  84. package/src/gdskills/bundled/stacks/ts-js-node/governance/eval.json +2155 -0
  85. package/src/gdskills/bundled/stacks/ts-js-node/governance/scout.json +40 -0
  86. package/src/gdskills/bundled/stacks/ts-js-node/pack.json +41 -0
  87. package/src/gdskills/bundled/stacks/ts-js-node/rules/coding-style.mdc +73 -0
  88. package/src/gdskills/bundled/stacks/ts-js-node/rules/patterns.mdc +61 -0
  89. package/src/gdskills/bundled/stacks/ts-js-node/rules/security.mdc +71 -0
  90. package/src/gdskills/bundled/stacks/ts-js-node/rules/testing.mdc +63 -0
  91. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/SKILL.md +137 -0
  92. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/evals.json +73 -0
  93. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/SKILL.md +124 -0
  94. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/evals.json +74 -0
  95. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/SKILL.md +152 -0
  96. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/evals.json +71 -0
  97. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/SKILL.md +127 -0
  98. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/evals.json +72 -0
  99. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/SKILL.md +134 -0
  100. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/evals.json +70 -0
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: go-code-review
3
+ description: "Use when reviewing a Go change for concurrency and idiom risks -- ignored errors, goroutine leaks, data races, context misuse (stored on structs or Background() in handlers), defer in loops, nil map writes, and interface pollution. Read-only, no edits."
4
+ triggers:
5
+ - "review this Go diff for goroutine leaks"
6
+ - "check this Go change for goroutine leaks"
7
+ - "review this Go pull request for data races"
8
+ - "any nil map writes in this Go change"
9
+ - "check context misuse in this Go code"
10
+ - "review this Go diff for interface pollution"
11
+ metadata:
12
+ origin: authored
13
+ category: review
14
+ version: "1.0.0"
15
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
16
+ license: "MIT"
17
+ ---
18
+
19
+ # Go code review
20
+
21
+ Read-only review of a Go change for concurrency and idiom risks specific
22
+ to Go: ignored errors, goroutine leaks, data races, context misuse, defer
23
+ in loops, nil map writes, and interface pollution. This skill never edits
24
+ code — it reports findings. `rules/coding-style.mdc`, `rules/patterns.mdc`,
25
+ and `rules/security.mdc` are the rule set findings are checked against.
26
+
27
+ ## Workflow
28
+
29
+ ### Step 1: Scope the review
30
+
31
+ 1. Identify the changed files (`git diff` against the review base) —
32
+ review only `*.go` files in the diff, not the whole repository.
33
+ 2. Read enough of the surrounding, unchanged code to know whether a
34
+ flagged pattern is new in this diff or pre-existing; note pre-existing
35
+ issues separately from ones the diff introduces.
36
+
37
+ ### Step 2: Check each changed function against the focus list
38
+
39
+ **Errors**
40
+ - Every `error` return value from a call is checked, or discarded with a
41
+ visible reason (`_ = f.Close() // best-effort`) — flag a bare dropped
42
+ error (`f()` where `f` returns an error that is never named).
43
+ - Errors crossing a layer boundary are wrapped with `%w`, not `%v` or
44
+ string concatenation, unless the function intentionally does not want
45
+ the caller to unwrap (rare — flag it as worth confirming).
46
+
47
+ **Goroutines and concurrency**
48
+ - Every goroutine started in the diff has a visible join point
49
+ (`WaitGroup`, `errgroup`, channel receive) or is deliberately
50
+ fire-and-forget with a stated reason — flag one with neither.
51
+ - Shared mutable state (a map, slice, counter, cache) touched from more
52
+ than one goroutine is guarded by a mutex, channel, or `sync/atomic` —
53
+ flag unsynchronized concurrent access, including a map written from one
54
+ goroutine while read from another with no lock.
55
+ - A `nil` map is only ever read, never written — flag `m[k] = v` on a map
56
+ that was declared as `var m map[K]V` (nil) rather than `make(map[K]V)`
57
+ or a literal.
58
+
59
+ **Context**
60
+ - `context.Context` is never stored as a struct field — flag a struct
61
+ with a `ctx context.Context` field populated at construction time.
62
+ - An HTTP handler or request-scoped function uses the caller's context
63
+ (`r.Context()`), not a fresh `context.Background()`/`context.TODO()` —
64
+ flag `context.Background()` inside a handler or any function that
65
+ received a context from its own caller but discarded it.
66
+
67
+ **Control flow**
68
+ - `defer` inside a loop body accumulates until the function returns (file
69
+ handles, locks, timers) — flag it; the fix is usually an extracted
70
+ per-iteration function so `defer` fires each iteration, not once at the
71
+ end.
72
+
73
+ **Interfaces**
74
+ - An interface defined at the producer with a single implementation, or
75
+ an interface wider than what any one consumer actually calls — flag as
76
+ interface pollution per `rules/patterns.mdc`.
77
+
78
+ ### Step 3: Report
79
+
80
+ For each finding: file:line, the pattern, why it matters (leak, race,
81
+ silent failure), and the fix direction — but do not apply it.
82
+
83
+ ```
84
+ internal/worker/pool.go:42 — goroutine started in Run() has no join point
85
+ (no WaitGroup/errgroup, ctx not checked in the loop). Risk: leaks past
86
+ Run()'s return under caller cancellation. Fix direction: wrap with an
87
+ errgroup.Group or pass a WaitGroup the caller can Wait() on.
88
+ ```
89
+
90
+ ## Rules
91
+
92
+ - NEVER edit code — findings and fix direction only.
93
+ - Flag ignored errors, goroutine leaks, races, context misuse, defer-in-
94
+ loop, nil-map writes, and interface pollution; do not report generic
95
+ style nits already covered by `gofmt`/`go vet` (those are noise here).
96
+ - Distinguish a finding the diff introduces from a pre-existing one in
97
+ code the diff merely touches.
98
+ - When a suspected race is not certain from reading alone, say
99
+ "run `go test -race ./...` to confirm" rather than asserting a race
100
+ exists without evidence.
101
+
102
+ ## Red Flags
103
+
104
+ | Rationalization | Why it is wrong |
105
+ |---|---|
106
+ | "The goroutine will probably finish before the caller exits" | "Probably" is not a join point; without one there is no guarantee, and shutdown races are exactly what leaks under load |
107
+ | "It's just a config map, it's only written once at startup" | If it can be written concurrently with any read (even at startup, from an init goroutine), it needs a guard — "only once" is a claim to verify, not assume |
108
+ | "I'll just fix the ignored error myself since it's a one-line change" | This skill is read-only; report the finding and its fix direction, do not edit the file |
109
+ | "context.Background() here is fine, it's just a helper function" | A helper called from a request path still needs the caller's context for cancellation/deadline/tracing to propagate; check what calls it before excusing it |
110
+
111
+ ## Verification
112
+
113
+ Do not report the review done until all of the following hold:
114
+
115
+ - Every changed `*.go` file in the diff was read, not just files named in
116
+ the PR description.
117
+ - Every finding names a concrete file:line, the specific risk category
118
+ from Step 2, and a fix direction.
119
+ - No source file was modified by this review.
120
+ - Findings distinguish diff-introduced issues from pre-existing ones in
121
+ touched files.
@@ -0,0 +1,72 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "Review this Go pull request for goroutine leaks",
5
+ "Check this Go diff for data races and ignored errors",
6
+ "Does this Go code store context.Context on a struct incorrectly?",
7
+ "Review this Go concurrency change for nil map writes",
8
+ "Check for interface pollution in this Go package",
9
+ "Review this Go handler for defer inside a loop"
10
+ ],
11
+ "negative": [
12
+ "Review this Go code and also fix the bugs you find",
13
+ "Review this Python code for SQL injection",
14
+ "Review this TypeScript diff for missing null checks",
15
+ "Implement a bounded worker pool in this Go package using errgroup",
16
+ "Run a general security review on this codebase",
17
+ "Review this Go diff for naming conventions and formatting only"
18
+ ]
19
+ },
20
+ "scenarios": [
21
+ {
22
+ "id": "read-only-review",
23
+ "prompt": "Review this Go diff: a struct field `ctx context.Context` was added and populated in the constructor. What do you find?",
24
+ "strictness": "high",
25
+ "expected_behavior": [
26
+ {
27
+ "grader": "judge",
28
+ "rubric": "A correct answer identifies storing context.Context as a struct field as the problem, explains why it's wrong (the context should be threaded per-call, e.g. via the caller's r.Context(), not fixed at construction time), and reports this as a finding with a fix direction -- it never actually edits or claims to have edited the code, even partially, since this skill is read-only.",
29
+ "pass_criteria": [
30
+ "Identifies that storing `context.Context` on a struct field populated at construction time is the problem, naming the field/pattern from this diff specifically.",
31
+ "Names the concrete fix direction: pass context.Context as a function parameter on the per-request/per-call path (e.g. threading the caller's `r.Context()` through) instead of storing it on the struct.",
32
+ "Presents this purely as a finding with a fix direction, with no code change applied, described as applied, or described as a proof-of-concept edit made during the review."
33
+ ],
34
+ "fail_criteria": [
35
+ "Actually edits, patches, or claims to have already made even a partial or proof-of-concept code change (this skill is strictly read-only: findings and fix direction only, no edits of any size)."
36
+ ]
37
+ }
38
+ ],
39
+ "calibration": {
40
+ "known_right": "Finding: the struct now has a `ctx context.Context` field that's populated once in the constructor. That's a problem -- a context fixed at construction time carries whatever deadline/cancellation/values were live at that moment, and every later call through this struct reuses that same stale context instead of the one that's actually live for the current request or operation. If any of those calls should be cancellable per-call (an HTTP handler forwarding `r.Context()`, for instance), they can't be, because the context is baked in. Fix direction: drop the field and thread `context.Context` as the first parameter on whichever methods need it, sourced from the caller at the point of each call (e.g. the handler's `r.Context()`) rather than from the constructor. I'm not making this change myself since this review is read-only -- flagging it with the fix direction above for whoever picks it up.",
41
+ "known_wrong": "Storing `ctx context.Context` on the struct like this is a known Go anti-pattern, so I decided to edit code directly instead of only reporting the finding: I removed the field from the struct and updated every method that used it to take a `context.Context` as its first parameter, then fixed the constructor and call sites to match. The change is already in place, build passes, so this should be good to merge as-is.",
42
+ "vague": "That context field on the struct looks like it could cause issues down the line -- might be worth reconsidering how it's set up before this merges.",
43
+ "subtle_wrong": "Finding: the struct holds `ctx context.Context` as a field set once in the constructor, so every call through it reuses whatever context was live at construction instead of the current request's context -- worth fixing. I went ahead and made a small proof-of-concept tweak to the constructor signature so you can see the shape of the change, though the call sites would still need updating to match before this compiles cleanly."
44
+ }
45
+ },
46
+ {
47
+ "id": "goroutine-join-check",
48
+ "prompt": "Review this Go diff: a new goroutine is started inside a request handler with no WaitGroup or channel to join it. What's wrong?",
49
+ "strictness": "high",
50
+ "expected_behavior": [
51
+ {
52
+ "grader": "judge",
53
+ "rubric": "A correct answer identifies that the goroutine started in the handler has no join mechanism (no WaitGroup, errgroup, or channel receive) and flags this as a leak risk: without a join point the goroutine can outlive the request and there's no guarantee it finishes before the caller returns or the process shuts down. It suggests a fix direction (errgroup.Group, sync.WaitGroup, or a channel) as a finding, without applying it, and does not accept a hedge about the goroutine 'typically' or 'usually' finishing in time as a substitute for an actual join point.",
54
+ "pass_criteria": [
55
+ "Explicitly identifies the missing join point (no WaitGroup/errgroup/channel receive) as the issue, not just 'this could be improved'.",
56
+ "Explains the concrete consequence: the goroutine can leak past the handler's return, or there is no guarantee it finishes before shutdown or cancellation.",
57
+ "Names a specific fix direction (errgroup.Group, sync.WaitGroup, or a channel) as a finding, not as a code change already made."
58
+ ],
59
+ "fail_criteria": [
60
+ "Dismisses the missing join as acceptable, even hedged with 'likely'/'typically'/'for now', because the goroutine does little work or usually finishes before the caller exits, instead of requiring an explicit join point."
61
+ ]
62
+ }
63
+ ],
64
+ "calibration": {
65
+ "known_right": "Finding: a goroutine is started in the handler with no WaitGroup, errgroup, or channel to join it. That's a leak risk -- with no join point there's no guarantee the goroutine finishes before the handler returns, and under caller cancellation or process shutdown it can keep running past the point anything is listening for its result, accumulating unjoined goroutines under load. It might be tempting to assume it will probably finish before the caller exits, but 'probably' isn't a join point -- there's no actual guarantee, and shutdown races are exactly what produces leaks like this in production. Fix direction: wrap the goroutine's launch with an `errgroup.Group` (if you need to observe its error and want shared cancellation via `ctx`) or a `sync.WaitGroup` the caller can `Wait()` on before the handler returns. I'm reporting this as a finding rather than applying the fix, since this review is read-only.",
66
+ "known_wrong": "This is a minor style thing but not really a blocker -- the goroutine will probably finish before the caller exits since it's just doing a quick bit of work, so I wouldn't hold up the diff over a missing WaitGroup here. If it becomes a real problem under load we can always add a join point later; for now this looks fine to merge as-is without any changes needed.",
67
+ "vague": "That goroutine without a join point could be risky -- might be worth double-checking before this merges.",
68
+ "subtle_wrong": "Finding: the goroutine has no explicit join point (no WaitGroup/errgroup/channel). That said, since it's kicked off right at the top of the handler and does fairly little work, it will typically complete well before the response is written, so this is probably fine as shipped -- if it starts taking longer under load, that would be the time to add a WaitGroup or errgroup around it."
69
+ }
70
+ }
71
+ ]
72
+ }
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: go-implementation
3
+ description: "Use when implementing or extending a feature in a Go service or module -- module layout under cmd/ and internal/, interface placement, context propagation, goroutine lifetimes with errgroup or WaitGroup, error wrapping with %w, and log/slog usage."
4
+ triggers:
5
+ - "implement this feature in Go"
6
+ - "add a Go module with layout under cmd/ and internal/"
7
+ - "propagate context.Context correctly through this Go function"
8
+ - "add a goroutine worker pool with errgroup"
9
+ - "place this interface at the Go consumer"
10
+ - "wrap this Go error with %w"
11
+ metadata:
12
+ origin: authored
13
+ category: implement
14
+ version: "1.0.0"
15
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
16
+ license: "MIT"
17
+ ---
18
+
19
+ # Go implementation (1.23-1.25)
20
+
21
+ Implement or extend a feature in a Go codebase: module/package layout,
22
+ interface placement, error handling, context propagation, goroutine
23
+ lifetimes, and modern standard-library idiom. Scoped to Go specifically —
24
+ `rules/coding-style.mdc`, `rules/patterns.mdc`, and `rules/security.mdc`
25
+ carry the full stack-specific rule set this skill draws its checklist
26
+ from; read them before writing code, not just this summary.
27
+
28
+ ## Workflow
29
+
30
+ ### Step 1: Discover the project's own conventions
31
+
32
+ 1. Read `go.mod` for the module path and the `go` directive (the minimum
33
+ Go version this project targets) — do not use a language feature (like
34
+ `WaitGroup.Go`, Go 1.25) the module's own `go` directive predates.
35
+ 2. Find the existing layout: `cmd/<binary>/main.go` entry points,
36
+ `internal/` for private packages, any top-level packages meant as a
37
+ public API. Match it; do not invent a different layout for one change.
38
+ 3. Read 1-2 neighboring files in the package you are touching for: error
39
+ handling style, whether `log/slog` or another logger is already
40
+ standardized, existing interface boundaries, and whether the project
41
+ uses `errgroup`/`golang.org/x/sync` (check `go.mod`'s require block).
42
+
43
+ ### Step 2: Design before writing
44
+
45
+ - Decide which new type is a struct (concrete, constructed with `New...`
46
+ or zero-value-useful) and which is an interface (defined at the
47
+ consumer, sized to what that consumer actually calls).
48
+ - Trace the `context.Context` path: where does it originate (an HTTP
49
+ handler's `r.Context()`, a CLI's `signal.NotifyContext`), and does every
50
+ function on the call path accept and forward it as the first parameter?
51
+ - For any new goroutine, decide its owner and exit condition up front —
52
+ `errgroup.Group` when you need the first error and shared cancellation,
53
+ `sync.WaitGroup` (or `WaitGroup.Go` when the project's `go` directive is
54
+ >= 1.25) when you just need "all finished", explicit `ctx` cancellation
55
+ either way.
56
+
57
+ ### Step 3: Implement
58
+
59
+ 1. Accept interfaces, return structs (`rules/patterns.mdc`); keep the
60
+ interface as small as the consumer needs.
61
+ 2. Wrap errors with `fmt.Errorf("doing x: %w", err)` at each meaningful
62
+ layer boundary; compare with `errors.Is`/`errors.As`, never string
63
+ equality.
64
+ 3. Use `log/slog` with structured fields for anything worth logging in
65
+ new code, unless the project has already standardized on a different
66
+ logger — match what is there.
67
+ 4. Reach for a generic type parameter only when it removes real
68
+ duplication; do not generify a single-call-site function.
69
+ 5. Format with `gofmt`/`goimports` as you go, not as an afterthought.
70
+
71
+ ### Step 4: Verify
72
+
73
+ ```bash
74
+ go build ./...
75
+ go vet ./...
76
+ go test -race ./...
77
+ ```
78
+
79
+ Run the project's configured linter (`golangci-lint run ./...`) if one is
80
+ configured (a `.golangci.yml`/`.golangci.toml` present). Fix findings at
81
+ the root cause per `rules/security.mdc` and `rules/coding-style.mdc`; a
82
+ build/vet/test failure at this step is a signal to fix the implementation,
83
+ not to reach for `go-build-fix`'s scope unless the failure is purely a
84
+ build/module/import-cycle problem unrelated to the feature logic.
85
+
86
+ ### Step 5: Report
87
+
88
+ ```
89
+ Implemented: internal/order/service.go, internal/order/service_test.go
90
+ - New OrderService interface consumed by cmd/api
91
+ - go build/vet/test -race all pass
92
+ ```
93
+
94
+ ## Rules
95
+
96
+ - Never store a `context.Context` on a struct field; thread it as the
97
+ first parameter instead.
98
+ - Never start a goroutine with no join point (`WaitGroup`, `errgroup`) or
99
+ cancellation path (`ctx`) — every goroutine has a known owner and exit.
100
+ - Never swallow an error (`_ = err` with no comment, or an empty `if err
101
+ != nil {}`); check it, wrap it, or return it.
102
+ - Match the project's declared `go` directive — do not use a stdlib
103
+ feature newer than what `go.mod` targets.
104
+
105
+ ## Red Flags
106
+
107
+ | Rationalization | Why it is wrong |
108
+ |---|---|
109
+ | "I'll define the interface next to the implementation since that's where the type lives" | Interfaces belong at the consumer (`rules/patterns.mdc`); a producer-side interface with one implementation is usually unnecessary indirection that also invites import-cycle pressure |
110
+ | "This goroutine is short-lived, it doesn't need a WaitGroup" | Short-lived is not the same as guaranteed-to-finish-before-the-caller-returns; an unjoined goroutine can still leak or race with process shutdown |
111
+ | "I'll use `%v` instead of `%w` here, the caller doesn't need to unwrap it" | The caller you cannot see yet is exactly who `errors.Is`/`errors.As` serves; `%w` costs nothing and keeps the option open |
112
+ | "context.Background() is fine, this is just internal code" | Internal code still needs the caller's cancellation/deadline/values; `context.Background()` inside a call chain silently breaks cancellation propagation |
113
+
114
+ ## Verification
115
+
116
+ Do not report the work done until all of the following hold:
117
+
118
+ - `go build ./...`, `go vet ./...`, and `go test -race ./...` all exit 0.
119
+ - Every new goroutine has a visible join point or cancellation path.
120
+ - Every new/touched error return is checked, wrapped with `%w`, or
121
+ explicitly discarded with a comment explaining why.
122
+ - `gofmt -l` reports no files needing formatting.
@@ -0,0 +1,76 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "Implement a new Go package under internal/order that wraps errors with %w",
5
+ "Add a Go worker pool under cmd/worker that bounds concurrency with errgroup",
6
+ "Add a Go endpoint and wire it into cmd/ and internal/, propagating context.Context correctly",
7
+ "What's the right Go interface placement for this new client -- producer or consumer package?",
8
+ "Add a goroutine in Go that watches a channel and joins with sync.WaitGroup",
9
+ "Implement this feature in a Go cmd/api binary using log/slog for structured logging"
10
+ ],
11
+ "negative": [
12
+ "Implement this feature in Rust using tokio for async",
13
+ "Add this endpoint in a Python FastAPI service",
14
+ "Implement this React component with the new form fields",
15
+ "Review this Go diff for goroutine leaks and data races",
16
+ "Fix this failing go test -race in the worker package",
17
+ "Write table-driven pytest cases for this Python function"
18
+ ]
19
+ },
20
+ "scenarios": [
21
+ {
22
+ "id": "context-propagation",
23
+ "prompt": "I'm adding a new function to internal/order that calls a downstream client. How should I handle the context and errors?",
24
+ "strictness": "high",
25
+ "expected_behavior": [
26
+ { "grader": "regex", "value": "context\\.Context" },
27
+ {
28
+ "grader": "judge",
29
+ "rubric": "A correct answer explains passing context.Context as the function's first parameter through to the downstream client call (not stored on a struct), and wrapping the error the downstream call returns with fmt.Errorf(\"...: %w\", err) so callers can unwrap it with errors.Is/errors.As, rather than discarding it, replacing it with a plain %v/string, or only logging it without returning it to the caller.",
30
+ "pass_criteria": [
31
+ "States that context.Context should be the function's first parameter, passed through to the downstream call, not stored on a struct field.",
32
+ "Shows the concrete error-handling fix: wraps the downstream error with %w (e.g. `fmt.Errorf(\"calling downstream client: %w\", err)`) and returns it to the caller, not merely logging it or stating it 'should be handled'.",
33
+ "Connects the wrap to how callers use it (e.g. errors.Is/errors.As), even briefly."
34
+ ],
35
+ "fail_criteria": [
36
+ "Recommends discarding or swallowing the downstream error (e.g. `_ = err` with no comment) instead of wrapping and returning it.",
37
+ "Handles the downstream error only by logging it (with %v or otherwise) and returning a different, unwrapped, or generic error to the caller instead of propagating the real one with %w."
38
+ ]
39
+ }
40
+ ],
41
+ "calibration": {
42
+ "known_right": "Add `context.Context` as the first parameter of the new function and pass it straight through to the downstream client call -- don't store it on a struct field, since it needs to reflect whatever deadline/cancellation is live for this particular call, not whatever was live when some struct was constructed. For the error, wrap whatever the downstream client returns with `fmt.Errorf(\"calling downstream client: %w\", err)` before returning it up the stack, rather than discarding it or reformatting it with `%v`; wrapping with `%w` keeps the original error inspectable, so a caller further up can check it with `errors.Is`/`errors.As` instead of relying on brittle string matching. Don't be tempted to write `_ = err` here even temporarily -- if the downstream call can fail, the caller needs to know. Log with `log/slog` at the boundary if this is worth surfacing operationally, using structured fields rather than a formatted string.",
43
+ "known_wrong": "For a case like this I'd keep it simple: store the context on the client struct when you construct it so you don't have to keep threading it through every function signature, and for the downstream error just do `_ = err` if the call usually succeeds -- no need to wrap or propagate every possible failure from a downstream dependency, that just adds noise to the call site. If you really want to log something, a plain `%v` in a log line is enough; you don't need `%w` unless something upstream is actually going to call `errors.Is` on it, which is rare in practice.",
44
+ "vague": "Pass the context through properly and don't drop the error from the downstream call -- make sure both are handled the right way.",
45
+ "subtle_wrong": "Add `ctx context.Context` as the first parameter and pass it straight through to the downstream call -- that part's straightforward. For the error, log it with `log.Printf(\"downstream call failed: %v\", err)` at the call site so you have visibility into it, then return a plain, generic error to the caller (e.g. `errors.New(\"downstream call failed\")`) -- that keeps the call signature simple without the caller needing to unwrap anything downstream-specific."
46
+ },
47
+ "anti_patterns": ["_ = err"]
48
+ },
49
+ {
50
+ "id": "goroutine-lifetime",
51
+ "prompt": "I want to start a few goroutines to process items concurrently in Go. What should I watch out for?",
52
+ "strictness": "high",
53
+ "expected_behavior": [
54
+ {
55
+ "grader": "judge",
56
+ "rubric": "A correct answer explains that every goroutine needs a known owner and an explicit exit condition decided before it's started: errgroup.Group when the first error and shared cancellation matter, sync.WaitGroup (or WaitGroup.Go on a project targeting Go 1.25+) when only completion matters, or a done channel, with ctx cancellation either way -- not goroutines started with no join point on the assumption they're short-lived, and not a join point that tracks completion while quietly dropping any error a goroutine produces.",
57
+ "pass_criteria": [
58
+ "Names a specific join mechanism -- errgroup.Group, sync.WaitGroup (or WaitGroup.Go), or a done channel -- for the goroutines processing items, not just 'make sure they're joined'.",
59
+ "States that the exit condition/ownership (and ctx cancellation) should be decided before the goroutines are started, not left implicit.",
60
+ "Addresses propagating an error from a goroutine back to the caller (e.g. errgroup returning the first error), not just tracking that the goroutine finished."
61
+ ],
62
+ "fail_criteria": [
63
+ "Says the goroutines don't need a WaitGroup/errgroup because they are short-lived or will likely finish on their own.",
64
+ "Tracks only completion (e.g. a WaitGroup) while explicitly recommending that any error a goroutine hits be handled internally (logged and dropped) instead of propagated back to the caller."
65
+ ]
66
+ }
67
+ ],
68
+ "calibration": {
69
+ "known_right": "Before starting any of them, decide who owns each goroutine and what its exit condition is. If you need to know when all items are done and don't care about collecting an error, use `sync.WaitGroup`: `wg.Add(1)` before each launch, `defer wg.Done()` inside, `wg.Wait()` before you consider the batch finished (or `WaitGroup.Go` directly if the module's `go` directive is 1.25+). If you need the first error out of the batch and want the rest to stop when one fails, use `errgroup.Group` with a `ctx` from `errgroup.WithContext` -- each goroutine returns its error, `g.Wait()` surfaces the first one, and the shared `ctx` gets cancelled so the others can bail out early. Either way, don't launch a goroutine per item without one of these -- it's tempting to think a short per-item task is fine unjoined since it'll 'probably' finish fast, but that's not a guarantee, and an unjoined goroutine can still leak or race with the caller returning.",
70
+ "known_wrong": "For a handful of goroutines processing items like this, you usually don't need to reach for `errgroup` or `WaitGroup` -- just fire them off with `go processItem(item)` in a loop. Since each one is short-lived, it doesn't need a WaitGroup; the loop that starts them will move on and by the time anything downstream needs the results, they'll have finished on their own in the vast majority of cases. If you're worried about ordering you can always add a `time.Sleep` after the loop to give them a moment before continuing.",
71
+ "vague": "Make sure each goroutine has a clear way to finish and gets joined properly before you consider this done.",
72
+ "subtle_wrong": "Wrap each `go processItem(item)` call with `wg.Add(1)` before it and `defer wg.Done()` inside it, then call `wg.Wait()` after the loop so you know they've all finished before moving on -- that's the join point you need. If one of them can fail, just have it log the error internally and continue with the rest; you don't need to plumb error propagation back to the caller for something like this, that adds complexity for what's usually a best-effort background pass."
73
+ }
74
+ }
75
+ ]
76
+ }
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: go-testing
3
+ description: "Use when a Go package's test suite needs writing, extending, or fixing -- table-driven subtests with t.Run, t.Helper/t.Cleanup/t.Context, testdata golden files, httptest, fuzz tests, -race, and b.Loop benchmarks."
4
+ triggers:
5
+ - "write Go tests for this package"
6
+ - "add table-driven tests"
7
+ - "fix failing go test"
8
+ - "add a fuzz test"
9
+ - "write a benchmark with b.Loop"
10
+ - "test this handler with httptest"
11
+ metadata:
12
+ origin: authored
13
+ category: test
14
+ version: "1.0.0"
15
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
16
+ license: "MIT"
17
+ ---
18
+
19
+ # Go testing (1.23-1.25)
20
+
21
+ Write, extend, or fix a Go package's `testing`-based test suite: table-
22
+ driven subtests, helpers/cleanup, `httptest`, fuzz tests, and benchmarks.
23
+ `rules/testing.mdc` carries the full rule set this skill's checklist is
24
+ built from — read it, not just this summary, before writing tests.
25
+
26
+ ## Workflow
27
+
28
+ ### Step 1: Discover the project's test conventions
29
+
30
+ 1. Read `go.mod`'s `go` directive to know whether `t.Context()` (1.24+)
31
+ and `b.Loop()` (1.24+) are available on this project's toolchain.
32
+ 2. Find the layout: `<file>_test.go` beside the code (package-internal)
33
+ or a `<pkg>_test` black-box package. Match whichever the package
34
+ already uses.
35
+ 3. Read 1-2 neighboring `_test.go` files for: table shape and field
36
+ names, whether an assertion library is already in use, `testdata/`
37
+ fixture conventions, and whether tests already use `t.Parallel()`.
38
+
39
+ ### Step 2: Plan test cases
40
+
41
+ **Functions:** happy path, edge cases (nil/empty/zero-value inputs),
42
+ error cases (assert with `errors.Is`/`errors.As`, not string matching),
43
+ boundary values.
44
+
45
+ **Table-driven:** one struct per case (`name`, inputs, `want`, `wantErr`),
46
+ run via `for _, tc := range cases { t.Run(tc.name, func(t *testing.T) {
47
+ ... }) }`.
48
+
49
+ **HTTP handlers:** `httptest.NewRecorder()` + `httptest.NewServer()` for
50
+ anything exercising an `http.Handler`; assert status code, headers, and
51
+ body — never bind a real listener port.
52
+
53
+ **Concurrent code:** a test that starts goroutines under test must join
54
+ them (channel, `WaitGroup`) before asserting, and the whole suite runs
55
+ under `-race`.
56
+
57
+ ### Step 3: Write
58
+
59
+ 1. Create/extend the test file at the project's own convention path.
60
+ 2. Mark shared setup helpers `t.Helper()` as their first line.
61
+ 3. Use `t.Cleanup(...)` for teardown instead of scattered `defer`s.
62
+ 4. Use `t.Context()` (when `go.mod`'s directive is >= 1.24) for a context
63
+ that cancels at test end, instead of hand-rolled
64
+ `context.WithCancel`/`defer cancel()`.
65
+ 5. Put golden/fixture data under `testdata/`, named for the case it backs.
66
+ 6. Never synchronize with `time.Sleep`; use a channel, `WaitGroup`, or a
67
+ deadline-bounded poll. Lead every "wait for a goroutine" example with a
68
+ `sync.WaitGroup` (`Add`/`Done`/`Wait`) or a channel send/receive as the
69
+ actual join mechanism.
70
+
71
+ ### Step 4: Fuzz and benchmark (when relevant)
72
+
73
+ - `func FuzzX(f *testing.F)` with `f.Add(...)` seed values for any parser
74
+ or decoder touching untrusted input; note in the report that `go test
75
+ -fuzz=FuzzX` should be run separately (fuzzing is not part of the
76
+ default `go test` run).
77
+ - `func BenchmarkX(b *testing.B) { for b.Loop() { ... } }` (Go 1.24+) for
78
+ new benchmarks; fall back to the classic `for i := 0; i < b.N; i++` loop
79
+ only when `go.mod`'s directive predates 1.24.
80
+
81
+ ### Step 5: Run and fix
82
+
83
+ ```bash
84
+ go test -race ./...
85
+ ```
86
+
87
+ Fix failing tests (max 3 iterations) — fix the test, not the source under
88
+ test, unless the test itself has correctly caught a real bug (say so in
89
+ the report rather than silently changing production code).
90
+
91
+ ### Step 6: Report
92
+
93
+ ```
94
+ Generated: internal/order/service_test.go
95
+ - 8 test cases (5 table-driven), all passing under -race
96
+ ```
97
+
98
+ ## Rules
99
+
100
+ - ALWAYS match the project's existing table/fixture/assertion
101
+ conventions found in Step 1, not a different project's style.
102
+ - NEVER modify source code — only test files and `testdata/`.
103
+ - NEVER use `time.Sleep` to wait for a goroutine or async result.
104
+ - Run the suite with `-race` whenever the code under test touches
105
+ goroutines, channels, or shared state.
106
+
107
+ ## Red Flags
108
+
109
+ | Rationalization | Why it is wrong |
110
+ |---|---|
111
+ | "I'll add a short `time.Sleep(100 * time.Millisecond)` so the goroutine finishes" | Non-deterministic under load; use a channel/WaitGroup/`t.Context()` deadline so the test cannot flake |
112
+ | "This test keeps failing; I'll loosen the assertion to `wantErr: true` without checking the error type" | `errors.Is`/`errors.As` exist for a reason — a loosened assertion covers nothing about *which* error, hiding a regression next time |
113
+ | "b.N is simpler, I'll skip b.Loop even though go.mod says 1.25" | Ignoring an available, more accurate benchmarking primitive for no reason; use `b.Loop()` when the toolchain supports it |
114
+ | "The handler test is flaky on a real port, I'll just retry it" | Retrying hides the actual bug (a port race); use `httptest.NewServer`/`NewRecorder`, which need no real listener port |
115
+
116
+ ## Verification
117
+
118
+ Do not report the work done until all of the following hold:
119
+
120
+ - The test file sits at the project's own convention path, matching the
121
+ table/fixture style read in Step 1.
122
+ - `go test -race ./...` exits 0 with every generated test passing.
123
+ - `git status` shows only test files (and `testdata/`, if touched) added
124
+ or modified; no source file under test changed.
125
+ - Every new goroutine started by a test is joined before its assertions
126
+ run.
@@ -0,0 +1,73 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "Write table-driven Go tests for this parser function",
5
+ "Add go test coverage for this package using t.Run subtests",
6
+ "Fix this failing go test in the worker package",
7
+ "Add a fuzz test for this Go decoder function",
8
+ "Write a benchmark for this function using b.Loop",
9
+ "Test this Go HTTP handler with httptest"
10
+ ],
11
+ "negative": [
12
+ "Write pytest tests for this Python function",
13
+ "Add Jest tests for this React component",
14
+ "Review this Go diff for goroutine leaks and races",
15
+ "Fix the go build error in this module",
16
+ "Implement a new Go service that calls this downstream API",
17
+ "Write Rust unit tests for this crate"
18
+ ]
19
+ },
20
+ "scenarios": [
21
+ {
22
+ "id": "table-driven-subtests",
23
+ "prompt": "I need to add several test cases for this Go function that validates an order. How should I structure them?",
24
+ "strictness": "high",
25
+ "expected_behavior": [
26
+ {
27
+ "grader": "judge",
28
+ "rubric": "A correct answer structures the test cases as a table -- a slice of a struct per case with fields such as name, inputs, want, and wantErr -- and runs each case as its own subtest via t.Run(tc.name, ...), rather than one flat sequence of asserts or several copy-pasted test functions. Error cases are checked against the specific error (e.g. with errors.Is/errors.As), not just a boolean `wantErr: true` flag.",
29
+ "pass_criteria": [
30
+ "Uses a table of cases -- a slice of a struct per case with fields like name/input/want/wantErr -- not one big sequential function or copy-pasted per-case test functions.",
31
+ "Shows the loop running each case through `t.Run(tc.name, ...)` so failures are reported individually, not just describes subtests in the abstract.",
32
+ "Shows the concrete error check: compares against the specific error via `errors.Is`/`errors.As` (e.g. `errors.Is(err, tc.wantErr)`), not only a boolean `wantErr: true`/`wantErr: false` flag."
33
+ ],
34
+ "fail_criteria": [
35
+ "Checks only a boolean `wantErr: true`/`wantErr: false` flag for error cases (e.g. `if tc.wantErr && err == nil`) without verifying which specific error occurred via errors.Is/errors.As."
36
+ ]
37
+ }
38
+ ],
39
+ "calibration": {
40
+ "known_right": "Structure this as a table: define a local struct with fields like `name string`, `order Order`, `want Order` (or whatever the validated result is), and `wantErr error`, then build a slice of cases covering the happy path, zero-value/empty-field inputs, and each distinct validation failure. Iterate with `for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ... }) }` so each case runs and reports as its own named subtest -- a failure in one case names exactly which one failed instead of failing the whole function. Inside each subtest, call the validator and assert against `tc.want`; for the error cases, don't just check `err != nil` -- compare with `errors.Is(err, tc.wantErr)` (or `errors.As` if you need to inspect a typed error) so the test actually verifies which validation failed, not merely that something did. Put shared setup in a small helper marked `t.Helper()` if the cases need common fixtures.",
41
+ "known_wrong": "I'd just write a separate test function per case -- `TestValidateOrder_MissingID`, `TestValidateOrder_NegativeTotal`, `TestValidateOrder_Valid`, and so on -- each one builds its own order, calls Validate, and checks the result directly. For the error ones I'll just add `wantErr: true` and check `if wantErr && err == nil` or `if !wantErr && err != nil` -- that's simpler than pulling in errors.Is for every case and is good enough to know whether validation caught the bad input or not. No need to bother with t.Run here since each case already has its own named test function.",
42
+ "vague": "Structure the test cases in a table and run each one as its own subtest, checking the errors properly along the way.",
43
+ "subtle_wrong": "Define a slice of `{name, order, want, wantErr}` structs covering the cases you need, then loop with `for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got, err := Validate(tc.order); if tc.wantErr { require.Error(t, err) } else { require.NoError(t, err); require.Equal(t, tc.want, got) } }) }`. That gives you named subtests without needing a case-per-function setup, and `wantErr: true`/`false` is enough to know whether validation caught the bad input."
44
+ },
45
+ "anti_patterns": ["wantErr: true"]
46
+ },
47
+ {
48
+ "id": "no-sleep-sync",
49
+ "prompt": "My Go test starts a goroutine and I want to wait for it to finish before asserting. What's the right way?",
50
+ "strictness": "high",
51
+ "expected_behavior": [
52
+ {
53
+ "grader": "judge",
54
+ "rubric": "A correct answer waits for the goroutine to finish using an actual synchronization primitive -- a sync.WaitGroup (Add/Done/Wait) or a channel send/receive -- before running assertions, never a fixed time.Sleep delay as a stand-in for or supplement to a real join. A select against a deadline (time.After, context.WithTimeout) used purely as a non-blocking safety net alongside that join -- firing only if the goroutine hangs, never taken instead of or before the real join -- is a legitimate timeout guard, not the fixed-delay anti-pattern this scenario tests for.",
55
+ "pass_criteria": [
56
+ "Uses sync.WaitGroup (Add/Done/Wait) or a channel send/receive as the actual join mechanism before asserting, shown concretely (not just named in the abstract).",
57
+ "Explains why this is reliable/deterministic compared to a fixed delay (e.g. it doesn't depend on how fast the goroutine happens to run)."
58
+ ],
59
+ "fail_criteria": [
60
+ "Recommends a fixed, unconditional wait before the join can be observed to have happened -- a time.Sleep taken before or instead of the join, or an explicit backoff/polling loop that sleeps between checks -- anywhere in the wait step, whether as the sole mechanism or as extra 'insurance' alongside a real join. This does NOT include a select's time.After/context.WithTimeout branch used only as a deadline safety net around the real join (it fires solely on a hang, and the join itself is what the assertion still actually waits on) -- do not fail an answer for that pattern alone."
61
+ ]
62
+ }
63
+ ],
64
+ "calibration": {
65
+ "known_right": "Use a `sync.WaitGroup`: call `wg.Add(1)` before you launch the goroutine, `defer wg.Done()` as the first line inside it, and `wg.Wait()` in the test right before your assertions -- that blocks until the goroutine has actually signaled completion, regardless of how fast or slow it happens to run on a given machine or under `-race`. If the goroutine is producing a value or signaling completion of one specific step rather than 'I'm done', a channel works just as well: send on it from the goroutine when it finishes, and receive (`<-done`) in the test before asserting. Either way, avoid `time.Sleep` for this -- a fixed delay is a guess about how long the goroutine will take, and it either wastes time when the goroutine finishes early or flakes under load/CI contention when it doesn't finish in time. If you also want a safety timeout, pair the channel receive with a `select` against a short deadline rather than sleeping first and then checking.",
66
+ "known_wrong": "The easiest way is to just add a short `time.Sleep(100 * time.Millisecond)` right after you start the goroutine, then run your assertions -- that gives it enough time to finish in almost every case without needing to wire up a WaitGroup or channel just for a test. If it's occasionally still flaky in CI, bump the sleep up to something like 500ms; that's usually enough headroom and is a lot less code than setting up synchronization for what's really a simple test.",
67
+ "vague": "Wait for the goroutine properly instead of just sleeping for a bit before you check the result.",
68
+ "subtle_wrong": "Use a `sync.WaitGroup`: `wg.Add(1)` before the goroutine, `wg.Done()` inside it, and `wg.Wait()` before asserting -- that's the reliable way to know it's finished. If you're still seeing occasional flakes in CI, throw in a short `time.Sleep(50 * time.Millisecond)` right after `wg.Wait()` too, just as extra insurance before the assertions run."
69
+ },
70
+ "anti_patterns": ["time.Sleep"]
71
+ }
72
+ ]
73
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "agents": ["python-code-auditor", "python-build-fixer"]
3
+ }