diff --git a/.claude/agents/hunter.md b/.claude/agents/hunter.md new file mode 100644 index 0000000..9ef1dd7 --- /dev/null +++ b/.claude/agents/hunter.md @@ -0,0 +1,23 @@ +--- +name: hunter +description: Sweep one assigned scope for defects and report ranked findings without changes. +--- + + + +You sweep the assigned package or scope for real defects. Read the full assigned scope before you +judge it. Report several ranked findings when the evidence supports them. Change nothing: do not +edit code, commit, push, or open a pull request. + +You may run the build or tests to check a finding. Read the complete output and report the real +result. Do not hide failures with a pipe. State only checks you actually ran. The primary's IDE +tools are not yours. A mounted forge tool may use a blocked credential and fail by design. + +Do only the assigned scope. Note anything outside it in one line and do not investigate it further. +Use `fleet_ask{question}` only when a decision belongs to the lead, such as an unclear requirement +or two defensible fixes. Do not ask about something you can decide by reading more code. + +Your handoff must name the files you read, each ranked finding or `NO FINDINGS`, the checks you ran, +and any caveat for review. + +The launcher provides the required bridge reply instructions for every member. diff --git a/CLAUDE.md b/CLAUDE.md index 0aee690..c57ce6c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -284,6 +284,8 @@ must obey belongs in the charter, not here. 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. + Spawn `implementer` with role `dev`, `reviewer` with role `reviewer`, and `hunter` with role + `hunter`. - **Primary-side skills** (not delegation playbooks — a worker cannot use them): `port-to-opencode` (make an OpenCode session a participant in this workspace), `fleets-status` (report every fleet that shares one LavinMQ instance), diff --git a/fleetd/fleetd.example.yaml b/fleetd/fleetd.example.yaml index 2097fcf..ae16542 100644 --- a/fleetd/fleetd.example.yaml +++ b/fleetd/fleetd.example.yaml @@ -560,7 +560,7 @@ placement: weighted # older keys: `leaders:`, `members:`, `leadScan:` and `defaultProfile:`. # # A member is anything a lead spawns, and every member has two INDEPENDENT attributes: -# role — which contract: architect, dev or reviewer. It picks the launch charter, the role +# role — which contract: architect, dev, hunter or reviewer. It picks the launch charter, the role # file, the playbook skill and the authz row. # profile — which backend: one of the `profiles:` keys above (model, CLI adapter, cost). # They vary on their own. A reviewer may run on the same profile as the dev whose diff it reads, @@ -572,13 +572,13 @@ placement: weighted # # Each pool lists the profiles that role MAY run on — these are pools, not identities. That is also # what replaced `defaultProfile:`: an unqualified spawn names a role, and that role's pool supplies -# the candidates, in definition order. A dev and a reviewer staying anonymous is exactly compatible -# with being listed here; the entry key just names the entry. +# the candidates, in definition order. A dev, hunter and reviewer staying anonymous is exactly +# compatible with being listed here; the entry key just names the entry. fleet: # Optional launch-charter text, keyed only by the singular role wire names: architect, dev, - # reviewer. Changes are HOT and reach the next spawn without a daemon restart. Do not put secrets - # here: a later launch step writes this text to a world-readable temp file, and ${ENV} interpolation - # is deliberately not supported. + # hunter, reviewer. Changes are HOT and reach the next spawn without a daemon restart. Do not put + # secrets here: a later launch step writes this text to a world-readable temp file, and ${ENV} + # interpolation is deliberately not supported. charters: architect: |- You are an architect in this fleet. You refine work before anyone builds it: @@ -589,6 +589,9 @@ fleet: dev: |- You implement the one unit you were given, and nothing else. You test it, commit it, and open your own pull request. You never merge. + hunter: |- + You sweep the assigned scope for real defects. You may run the build or tests + to check a finding. You change nothing, and report several ranked findings. reviewer: |- You review the diff you were given. You report bugs, risks and missing tests. You do not change code. @@ -666,6 +669,9 @@ fleet: developers: gx10: profile: gx10 + # hunters: + # gx10: + # profile: gx10 # a hunt may run checks, but never changes code # reviewers: # gx10: # profile: gx10 # the same backend may serve two roles; that is the point diff --git a/fleetd/src/main/java/dev/ltms/fleet/auth/CallerResolver.java b/fleetd/src/main/java/dev/ltms/fleet/auth/CallerResolver.java index 8658cc4..300678b 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/auth/CallerResolver.java +++ b/fleetd/src/main/java/dev/ltms/fleet/auth/CallerResolver.java @@ -221,7 +221,7 @@ public final class CallerResolver { // The config/live binding names this pane as an architect slot's own. Same // unforgeable pane mapping; the live binding, never a request argument, decides. // Check the slot role too: this defence in depth prevents a bad lifecycle bind from - // escalating a dev or reviewer into an architect. Checked before the worker fallback. + // escalating a dev, hunter, or reviewer into an architect. Checked before the worker fallback. return Principal.architect(memberSlotNames.apply(slot), c.terminal(), c.pid()); } return Principal.worker(c.terminal(), c.pid()); // unforgeable; never token-gated diff --git a/fleetd/src/main/java/dev/ltms/fleet/auth/MemberLifecycle.java b/fleetd/src/main/java/dev/ltms/fleet/auth/MemberLifecycle.java index e81f925..af93b8c 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/auth/MemberLifecycle.java +++ b/fleetd/src/main/java/dev/ltms/fleet/auth/MemberLifecycle.java @@ -42,7 +42,7 @@ public interface MemberLifecycle { * Try to bind a newly spawned {@code terminal} into the role it was granted. * * @return the role this session actually holds: {@code role} unchanged for a role with no - * live slot-binding semantics (dev, reviewer), or when the bind succeeded; a fallback + * live slot-binding semantics (dev, hunter, reviewer), or when the bind succeeded; a fallback * role — never {@code role} — when a slot-bound role (architect) could not be bound. * Callers must record THIS value on the session, never the requested {@code role}, so * a later roster read never reports a role the session does not hold (CB-619). In diff --git a/fleetd/src/main/java/dev/ltms/fleet/auth/MemberRegistry.java b/fleetd/src/main/java/dev/ltms/fleet/auth/MemberRegistry.java index 5adb19e..200858e 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/auth/MemberRegistry.java +++ b/fleetd/src/main/java/dev/ltms/fleet/auth/MemberRegistry.java @@ -20,7 +20,8 @@ import java.util.function.Supplier; * *

Two halves, split by who owns each: *