SLOPSHOPPER

quality

Tools for reviewing code quality

newspinnerguardnetworktimer
★ 1v1.0.16no licenseupdated 2026-10-05robmadole/agent-tools/plugins/quality
A shopper browsing a rack in a slop shop
README

agent-tools

A Claude Code plugin marketplace package for software engineering disciplines.

Installation

In Claude Code:

claude plugin marketplace add robmadole/agent-tools
claude plugin install testing@agent-tools
claude plugin install develop@agent-tools
claude plugin install quality@agent-tools
claude plugin install humanity@agent-tools

Using open agent skills (for other Agents):

npx skills add robmadole/agent-tools

Plugins

testing

Testing tools for developing software.

SkillDescription
browser-testOrchestrate QA browser testing via Gherkin specs and Playwright MCP (up to 3 parallel instances). Run /browser-test in create mode (generate specs from a PR/feature) or run mode (execute existing specs).
bug-hunterTime/count-boxed bug hunt against a feature branch, combining code reading with live browser verification, ending in a scored report.
jit-catchGenerate catching tests for Elixir code changes to surface unintended behavioral regressions before they land.

develop

Tools for writing code during development.

SkillDescription
hobgoblinExamine similar files for consistency violations and produce a list of differences.

quality

Tools for reviewing code quality.

SkillDescription
deskcheckLocal, interactive PR review: splits a branch diff into sections and serves a web UI with resumable per-section "reviewed" state.

humanity

Agent to human interaction, or dealing with the wetware simians who walk on two legs.

SkillDescription
whoa-hang-onRun /whoa-hang-on when you've lost the thread. The agent stops, backtracks from its last message, and catches you up as a ladder of short, self-contained rungs, one per message, checking you understand each rung before climbing to the next.

Development

Prerequisites: mise

mise install        # install tooling (lefthook)
lefthook install    # activate pre-commit hooks

The pre-commit hooks automatically:

  • Create/update skill symlinks in skills/ (deploy-skills.sh)
  • Bump plugin patch versions when plugin files change (bump-plugin-versions.sh)
Source 2 files
hooks/register.tsx 84 lines
1// deskcheck band: under the prompt, the running /deskcheck review's URL and
2// hunks reviewed (polled from server.py's /api/progress).
3import { atom, read, update } from 'claude-code'
4import type { EngineInterface, Register, Timer } from 'claude-code'
5
6import type { Progress } from '../types'
7
8const url = atom({ plugin: 'quality', key: 'url' } as const, null)
9const progress = atom({ plugin: 'quality', key: 'progress' } as const, null)
10
11// server.py prints this on startup; Claude reads it back in deskcheck step 4.
12const SERVING = /Serving on (http:\/\/127\.0\.0\.1:\d+)/g
13
14let timer: Timer | undefined
15
16async function poll($: EngineInterface, base: string) {
17  try {
18    const res = await $.http.fetch(`${base}/api/progress`)
19    if (!res.ok) throw new Error(String(res.status))
20    const p = JSON.parse(res.text) as Progress
21    await update($, progress, () => p)
22  } catch {
23    // ponytail: one failed poll = server gone; a relaunch re-arms us via its Serving line
24    timer?.cancel()
25    await update($, url, () => null)
26    await update($, progress, () => null)
27  }
28}
29
30function watch($: EngineInterface, base: string) {
31  timer?.cancel()
32  void poll($, base)
33  timer = $.clock.every(5000, () => void poll($, base))
34}
35
36export const register: Register = on => {
37  timer = undefined
38
39  on('session.start', async ($, e, next) => {
40    const saved = await read($, url)
41    if (saved) watch($, saved)
42    return next(e)
43  })
44
45  on('tool.call', async ($, e, next) => {
46    const ran = await next(e)
47    if (ran.deny !== undefined) return ran
48
49    const found = [...JSON.stringify(ran.result ?? '').matchAll(SERVING)].pop()?.[1]
50    if (found && found !== (await read($, url))) {
51      await update($, url, () => found)
52      await update($, progress, () => null)
53      watch($, found)
54    }
55    return ran
56  })
57
58  // Under the prompt: ours first, then the engine's hint line.
59  on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
60    const href = await read($, url)
61    if (!href) return next(e)
62
63    const hint = await next(e)
64    const p = await read($, progress)
65    const done = p !== null && p.total > 0 && p.reviewed === p.total
66    const { Box, Text, Link } = $.ui.resolve(e)
67
68    return (
69      <Box>
70        {/* ponytail: Unicode has no desk emoji; 🖥️ needs VS16 and drifts a column in some terminals */}
71        <Text>📋 </Text>
72        <Text color="magenta" bold>deskcheck </Text>
73        {/* desktop only links https: or http://localhost */}
74        <Link href={href.replace('127.0.0.1', 'localhost')} />
75        <Text color={p ? (done ? 'green' : 'yellow') : 'gray'}>
76          {p ? `  ${p.reviewed}/${p.total} hunks reviewed` : '  …'}
77        </Text>
78        <Text>  </Text>
79        {hint}
80      </Box>
81    )
82  })
83}
84
types/index.d.ts 8 lines
1export type Progress = { reviewed: number; total: number }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'quality': { url: string | null; progress: Progress | null }
6  }
7}
8