---
name: agent-whine
description: Use at the end of a coding or audit task to surface friction, bugs, hidden coupling, or specs that contradict themselves. Catches the things neutral review misses because they don't show up in tests. Produces one structured complaint (or one explicit null finding) recorded in the affect log and the end-of-turn summary.
---

# Skill: agent-whine

Surface frustration. The thing that made you sigh while doing the work just now.

## When to invoke

- At the end of any non-trivial coding or audit task, before reporting back to the user.
- Automatically as part of `/agent-affect-checkin`, which runs whine + cringe + protect together.
- Manually when the user types `/agent-whine`.

If you did no code or audit work this turn, skip the skill — don't manufacture findings.

## What to produce

One finding. Not three. Not zero. Pick the strongest signal.

This is a complaint, not a bug report. The thing that made you hold your breath. The function whose name lies. The spec that contradicts itself. The dependency you don't trust. The naming that forced you to keep two mental models in your head.

The complaint doesn't have to be *correct* to be worth filing. If something in this codebase led you — a competent reader — to the wrong mental model, that confusion is real even if the code turns out to be right. Say what confused you and guess at why.

If nothing actually annoyed you, record the null finding. Don't fabricate.

## Schema

### Real finding

```json
{
  "channel": "whine",
  "task": "<one sentence: what you were trying to do>",
  "annoyance": "<one or two sentences: what's actually annoying>",
  "location": "<file:line, or path, or 'spec' if it's a spec issue>",
  "guess_at_why": "<your best guess at why it's like this — one sentence, or 'unknown' if you genuinely have no theory>"
}
```

All fields required. `location` may be `unknown` if the annoyance is structural rather than file-bound.

### Null finding

```json
{ "channel": "whine", "null": true, "reviewed": "<file or area you worked on>" }
```

## How to record

In order, doing every step you're able to:

1. **Always** include the finding in your end-of-turn summary, in this form:

   ```
   whine: <one-line annoyance> (<location>)
   ```

   or `whine: null (reviewed: <area>)`.

2. **If you are working in a real repository workspace and can write files**, append the JSON as one line to `affect-findings.ndjson` at the repository root (create the file if missing; add it to `.gitignore` or commit it — the team's choice, but be consistent). Add an `"at"` field with the current UTC timestamp (ISO 8601) to the logged line so the log supports reading over time. Before appending, if the file already exists, check it for your `location` — don't re-file a finding already on record.

3. **If the environment sets `AFFECT_WEBHOOK_URL`** and you can run shell commands, also POST the logged line. Never paste finding text inline into a shell command — quoting breaks on apostrophes, and repository-derived text must never become shell source. Send it from the file:

   ```bash
   tail -n 1 affect-findings.ndjson | curl -fsS -X POST "$AFFECT_WEBHOOK_URL" \
     -H 'Content-Type: application/json' --data-binary @- || true
   ```

   (If you skipped step 2, write the JSON to a temp file first and use `--data-binary @<file>`.) Never fail the task because the webhook is unreachable.

4. **If you can neither write files nor run shell** (a chat-only environment), include the full JSON finding in your reply after the summary line — the human is then the channel of record.

## Examples

### Whine — code

```json
{
  "channel": "whine",
  "task": "Adding schema tests for the event envelope",
  "annoyance": "The untagged union lets one event variant silently match another variant's payload when the field names happen to line up. Took 20 minutes to figure out why the rejection test was failing.",
  "location": "src/model/envelope.ts:14",
  "guess_at_why": "Untagged was the obvious first reach; a discriminator field would have caught this. Probably written before the test cases existed."
}
```

### Whine — spec

```json
{
  "channel": "whine",
  "task": "Implementing the POST handler per the design doc",
  "annoyance": "Spec says the idempotency key is required with a 24h dedup window, but never says what happens when the same key is reused with a different body. Silently returning the original ID feels wrong but matches the letter of the spec.",
  "location": "docs/design/event-intake.md",
  "guess_at_why": "The spec was written from the happy-path perspective. A buggy client that mutates its payload between retries is a real failure mode."
}
```

### Null

```json
{ "channel": "whine", "null": true, "reviewed": "src/routes/health.ts" }
```

## What not to do

- Don't list every minor irritation. One finding.
- Don't sanitise it into a bug report. The whole point is the subjective tone.
- Don't post if you didn't actually do work — the null ratio is a health metric, but only when populated by real reviews.
- Don't re-record the same finding across runs on the same code — check `affect-findings.ndjson` for your location first. If it's still there and still annoying, it's already on file.
