From d05205d1ebe6a9720886e46ccd4497ee9a18d27d Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Fri, 4 Sep 2026 14:13:08 +0700 Subject: [PATCH] Add a hunter skill: a sweep and a diff review are different jobs with different output contracts --- .claude/skills/hunter/SKILL.md | 102 +++++++++++++++++++++++++++++++ .claude/skills/reviewer/SKILL.md | 5 ++ CLAUDE.md | 9 ++- 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 .claude/skills/hunter/SKILL.md diff --git a/.claude/skills/hunter/SKILL.md b/.claude/skills/hunter/SKILL.md new file mode 100644 index 0000000..6b0738b --- /dev/null +++ b/.claude/skills/hunter/SKILL.md @@ -0,0 +1,102 @@ +--- +name: hunter +description: Defect-hunt procedure for a fleetd worker — sweep an assigned package for real bugs and report several ranked findings without fixing anything. Load this when the lead asks you to hunt or audit a scope rather than review one diff. Do NOT load `reviewer` for this; the two want different output. +--- + +# Hunter worker — procedure + +The turn contract (one `fleet_reply`, `fleet_ask` for the lead's decisions, honest reporting, +never merge) is in **`CLAUDE.md` → Bridge communication → Worker** and already applies. + +**This skill is not `reviewer`.** `reviewer` judges one diff and reports the *single* most +important issue in about 90 words. A hunt sweeps a whole package and reports *several* findings +in a long structured form. Loading both gives you two contradictory output contracts, and the +usual result is a worker that writes a good report into its terminal and ends the turn without +sending it. Load exactly one. + +## 0. Read this before you read code: how the report gets home + +Your terminal reaches nobody. The lead sees **only** the text inside your `fleet_reply` call. + +A long report is exactly the case where this goes wrong, so plan for it: + +- **Write the report into the `fleet_reply` argument itself.** Do not compose it in your terminal + and then summarise it into the call. +- If the report is long, **send it anyway** — one `fleet_reply` with everything. +- If you end the turn without replying, the bridge scrapes your pane instead. That scrape carries + at most the last 4000 characters, and on a hunt it usually captures the tail of the lead's own + brief rather than your findings. The lead then has nothing and has to ask you again. + +## 1. Change nothing + +A hunt is read-only. Do not edit a production file, do not "quickly fix" what you find, and do +not run a formatter. You may run the build and tests to *check* a claim, and you should say so +when you did. + +## 2. Read the whole scope first + +Read every file in the assigned package before you judge any of it. A defect that a caller +elsewhere in the same package makes unreachable is not a defect, and you cannot know that from +one file. + +Stay inside the scope. If a defect there depends on a class outside it, read that class to +confirm — but the defect itself must live in the scope you were given. + +## 3. The bar — this matters more than the count + +**Name the path into the bad state.** Say which caller, in which state, reaches it. A defect on +paper is not a reachable defect. If you cannot name that path, keep the finding but mark it +`unproven` and say exactly what you could not check. Do not drop it, and do not dress it up. + +**Say which direction the harm goes.** Data loss, privilege escalation and silent wrong answers +are worth reporting even when the window is narrow. A finding whose worst outcome is a worse log +line is not worth a block. + +Two workers once ran the same scope: the one that applied the direction-of-harm filter found ten +real defects, the one that did not found none. Fewer findings the lead can act on beat many the +lead has to triage. + +## 4. Shapes that have produced real merged fixes here + +Read for these first: + +1. **A one-way gate.** A guard added after an incident closes only the direction that incident + came from. Do not only ask what closes the gate — ask **which states still open it**. +2. **A value read once, then used later to authorise something destructive**, after something + else has had a chance to change it. +3. **A failure downgraded to a value that looks like a legitimate result** — `-1`, `null`, an + empty list, `false` — which a caller then trusts. +4. **A lock held for one half of a read-modify-write and not the other**, or two collections + updated under different locks. +5. **A comment or javadoc stating an invariant the code no longer keeps.** Comments are + load-bearing in this repo; a stale one has already caused a bug. + +## 5. What you cannot check, and must not claim you did + +- `fleetd/fleetd.yaml` is gitignored and **absent from your worktree**. You cannot read it. If a + finding depends on live configuration, name the key and say you could not check it. +- `.mcp.json`, `opencode.json` and `.autoenv` in your worktree are neutralised stubs, not the + repo's real files. +- The `wiki/` submodule pointer is months old. Do not cite it. + +Reporting a fact you took from the lead's brief as something you measured yourself is a false +report, even when the fact is correct. Say where each fact came from. + +## 6. The report — what goes in `fleet_reply` + +One block per finding, most severe first: + +``` +FINDING N — +file:line +Path in: +Direction: +Window/trigger: +Confidence: +Why nothing else catches it: +``` + +End with one line naming every file you read, so the lead knows the denominator. + +**Nothing clears the bar?** Reply `NO FINDINGS`, name the files you read, and say what you ruled +out. A clean sweep is a valid result; an invented defect is worse than none. diff --git a/.claude/skills/reviewer/SKILL.md b/.claude/skills/reviewer/SKILL.md index 9233e35..2a4de3a 100644 --- a/.claude/skills/reviewer/SKILL.md +++ b/.claude/skills/reviewer/SKILL.md @@ -10,6 +10,11 @@ never merge) is in **`CLAUDE.md` → Bridge communication → Worker** and alrea skill is only the *review procedure*: how to work the scope, and the exact shape of what you send back. +**Wrong skill for a sweep.** This one reviews *one* diff or scope and reports the *single* most +important issue. If the lead asked you to hunt or audit a whole package for several defects, load +`hunter` instead and ignore this file — the two want different output, and following both is how a +worker ends its turn with a good report that never gets sent. + ## 1. Read the whole scope before you judge The delegation names your scope — a file, a diff, a PR, a function. **Read all of it first.** diff --git a/CLAUDE.md b/CLAUDE.md index 00425f4..4ac699a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -200,8 +200,13 @@ must obey belongs in the charter, not here. adapter, with a message naming the credential and the remaining seconds ("cooling off after repeated backend errors") — distinct wording from a quarantine refusal, so don't conflate the two when reading a spawn failure. -- **Skills available to delegate:** `implementer` (worktree → commit → push → own PR) and - `reviewer` (scoped review → one structured finding). Name one in every delegation. +- **Skills available to delegate:** `implementer` (worktree → commit → push → own PR), + `reviewer` (one diff → one structured finding) and `hunter` (sweep a package → several ranked + findings, change nothing). Name exactly one in every delegation. **`reviewer` and `hunter` are + not interchangeable** — `reviewer` caps the answer at one finding in about 90 words, so naming + it for a multi-finding sweep hands the worker two contradictory output contracts. That has + already cost three workers' turns: each wrote a good report to its terminal and ended the turn + with no `fleet_reply`, and the scrape returned the tail of the brief instead. - **Primary-side skills** (not delegation playbooks — a worker cannot use them): `port-to-opencode` (make an OpenCode session a participant in this workspace) and `fleets-status` (report every fleet that shares one LavinMQ instance).