Produce a grounded, ranked improvement shortlist for a workspace project and persist it to goblin's brain/ atom store. Three mutually-exclusive modes: --small (default — small, purely-additive, non-disruptive quick wins), --big (full-fidelity improvement ideas), and --uat (triage the project's operator-asks for AI takeover). Reads the project's real state, generates anchored candidates, scores/triages them with parallel LLM judges, ranks, and writes mode-discriminated atoms.
Installs into .claude/skills of the current project.
Are you the author of Goblin Suggest?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/aberson-goblin-suggest)
---
name: goblin-suggest
description: Produce a grounded, ranked improvement shortlist for a workspace project and persist it to goblin's brain/ atom store. Three mutually-exclusive modes: --small (default — small, purely-additive, non-disruptive quick wins), --big (full-fidelity improvement ideas), and --uat (triage the project's operator-asks for AI takeover). Reads the project's real state, generates anchored candidates, scores/triages them with parallel LLM judges, ranks, and writes mode-discriminated atoms.
user-invocable: true
---
# goblin-suggest
> **Reference skill — requires a private "second brain" + the `goblin` CLI, both omitted here.**
> Published as a **design reference**, not runnable as-is: it reads/writes a per-project
> second-brain atom store (`brain/suggestions/*.md`) that is **not included, for privacy**, and it
> is driven by the **`goblin` CLI** (a separate project) that is likewise not part of this repo. To
> reconstruct a working version, see [`_shared/goblin-second-brain.md`](../_shared/goblin-second-brain.md)
> — it documents the atom shape and how to build your own second brain. The design below is unedited.
Run goblin's full project-improvement pipeline against one workspace project. The skill
grounds in the project's REAL state (CLAUDE.md + plan + open issues + memory), generates
candidate improvements anchored to real artifacts, scores each candidate with a parallel
four-axis LLM-judge rubric, ranks the survivors, and persists them as git-tracked
suggestion atoms under `brain/suggestions/`.
This is the v1 keystone (Phase D). When invoked from a Claude Code session, the LLM
steps run via the `Workflow` tool (`goblin_suggest.workflow.js`) — all `agent()` calls
stay on subscription OAuth (`CLAUDE_CODE_OAUTH_TOKEN`), not per-token billing. The
CLI fallback (`uv run goblin suggest <project>`) shells to `claude -p` subprocess calls
and remains available for non-session/offline use.
## Prerequisites
- `CLAUDE_CODE_OAUTH_TOKEN` is set in the environment (subscription OAuth — NOT an API
key; the API key would double-bill).
- `gh auth status` shows you are logged in. Grounding reads the target project's open
issues via `gh`; a project with no remote / no auth just yields zero issues (the run
still works, with less context).
- `uv sync` has been run in the `b2_project_goblin` directory (installs the `goblin`
console script + deps).
- The target project is a direct child of the dev workspace root and has a `CLAUDE.md`
and a discoverable plan doc (grounding fails loud otherwise).
## How to invoke
**Session path (recommended — runs via Workflow, stays on subscription OAuth):**
```javascript
// From a Claude Code session — invoke the Workflow directly:
Workflow({
scriptPath: ".claude/skills/goblin-suggest/goblin_suggest.workflow.js", // part of the goblin CLI project (not in this repo)
args: { project: "toybox", mode: "small", n_candidates: 5, n_judges: 3 }
})
```
Or via the `/goblin-suggest` skill shorthand when invoked interactively.
**CLI fallback path (non-session / offline use — shells to `claude -p`):**
From the `b2_project_goblin` directory:
```powershell
uv run goblin suggest <project>
```
Examples:
```powershell
uv run goblin suggest toybox # --small (default): additive quick wins
uv run goblin suggest void_furnace --big --n-candidates 5 --n-judges 3
uv run goblin suggest toybox --uat # triage operator-asks for AI takeover
```
Flags:
- `--small` / `--big` / `--uat` — the generation mode (mutually exclusive; default
`--small`). See **Modes** below. Supplying two at once is an argparse error.
- `--n-candidates N` — how many candidates to generate (default 5). Applies to
`--small` / `--big` only; `--uat` emits one task per distinct operator-ask, so it
ignores this flag.
- `--n-judges N` — judge runs per candidate/task (default 3). Each candidate (or UAT
task) must clear N valid verdicts or it is dropped (the partial-verdict guard).
- `--brain-dir <path>` — override the atom store (default: the goblin repo's `brain/`).
## Modes
`goblin suggest` has three mutually-exclusive modes. Each produces a SEPARATE ranked
list and persists its own atom kind; you pick at most one (the default is `--small`).
- **`--small` (NEW DEFAULT)** — small, purely-additive, non-disruptive quick wins: edits
that need no- or at-most-one clarifying question to start and don't disrupt existing
behavior. The cheap/fast path for a bare `goblin suggest <project>`.
- **`--big`** — the original full-fidelity, bigger-improvement-idea behavior (today's
single-prompt generation). Use when you want substantial improvement proposals, not
just additive quick wins.
- **`--uat`** — a DIFFERENT activity, not a different-sized suggestion. It scans the
project's **operator-asks** — `## Manual UAT` / `### M<n>:` plan blocks,
`**Type:** operator|wait` steps, and operator-handoff cue phrases ("please run",
"operator confirms/runs", "run X next") — and triages each one for AI takeover (could
an AI do this end-to-end? what's the single question to start? what human-only residual
remains?). It persists `uat-*.md` atoms, NOT suggestion atoms.
`--small` and `--big` share the 4-axis rubric and the suggestion pipeline; `--uat` runs
a DISTINCT pipeline with its own triage (no 4-axis scores). See **What it does** below.
## What it does (pipeline stages)
### `--small` / `--big` (the suggestion pipeline)
1. **ground** — validate the project arg, read its real CLAUDE.md + plan + open issues +
memory atoms. All fetched content is enveloped as untrusted DATA (prompt-injection
defense) before any model sees it.
2. **generate** — build one generation prompt (`goblin suggest --export-prompts`), then
call ONE `agent()` (session path) or `claude -p` (CLI fallback). Parse the model's
JSON array into candidates. The prompt's instruction framing branches on the mode:
`--small` asks for small, purely-additive, non-disruptive quick wins; `--big` asks for
the original bigger-improvement ideas. Each candidate must be anchored to a REAL
artifact (file path / issue number / plan line); ungrounded ones are rejected at parse
time.
3. **grade** — for EACH candidate, fan out `--n-judges` judge agent calls IN PARALLEL
(session path: `parallel()` in the Workflow; CLI path: a ThreadPoolExecutor per
candidate), each scoring the four rubric axes (feasibility, fit, leverage,
reversibility) 1–5. Per-axis median over the valid verdicts; composite = mean of the
four axis-medians. Grading does NOT branch on mode — the 4-axis rubric is shared across
`--small` and `--big`. A candidate with fewer than `--n-judges` valid verdicts is
DROPPED, never scored on a partial set.
**Optional local-judge offload (switchboard, INERT BY DEFAULT).** This judge fan-out is
the one role this skill may route to a local model (tier-offload task_class
`goblin-suggest-judge`; Switchboard Decision 9 — a cheap parallel rubric-scoring array).
It is **off unless switchboard offload is enabled for this slice**. For each judge call,
first consult the switchboard judge entrypoint with the rubric prompt:
`python -m switchboard judge --site goblin-suggest-judge --prompt-file <judge-prompt-file>`
(prints one JSON object, always exits 0). On a **verdict**, use it as that judge's
advisory vote. On a **defer** (`{"defer": true, ...}`) — ALWAYS the case when offload is
off, the slice is disabled, or the model is down/slow/wrong-shaped — fall back to the
normal Claude judge agent for that vote. When offload is OFF (the default), the entrypoint
returns a defer immediately with no network call, so grading runs on Claude **exactly as
before**. The local model only advises an axis vote; the median/rank/persist logic is
unchanged and never gated by the local model.
4. **rank** — order the survivors by composite score, descending (deterministic
tie-break by title).
5. **discrimination guard** — if every candidate scored nearly the same, the rubric
measured nothing; the run flags the ranking as non-discriminating.
6. **persist** — write each ranked candidate as a `brain/suggestions/sugg-<project>-<slug>.md`
atom (atomic write, update-not-duplicate — a re-run updates the same file rather than
creating a dated duplicate). Each atom is tagged with the `mode` it was produced under.
### `--uat` (the operator-ask triage pipeline)
A DISTINCT pipeline with its own triage — no 4-axis rubric, no discrimination guard:
1. **ground (uat)** — scan the project's already-neutralized sources for **operator-asks**
(`## Manual UAT` / `### M<n>:` plan blocks, `**Type:** operator|wait` steps, and
operator-handoff cue phrases like "please run" / "operator confirms/runs" / "run X
next"); emit one ask per detected unit of operator work.
2. **generate (uat)** — call ONE `agent()` (session path) or `claude -p` (CLI fallback)
to turn the operator-asks into UAT tasks (one task per distinct ask; `--n-candidates`
does NOT apply). Each task is anchored to the source ask; ungrounded ones are rejected
at parse time.
3. **assess (uat)** — for EACH task, fan out `--n-judges` judge agent calls IN PARALLEL. Each
judge votes just two consensus fields: `ai_doable` (can an AI do this end-to-end with
the project's substrate, no human?) and a 3-valued clarifying-question-count ordinal
(`none` < `one` < `many`). Consensus = bool MAJORITY (a tie resolves to **needs-human**,
the conservative default) + ordinal MEDIAN. A task with fewer than `--n-judges` valid
verdicts is DROPPED.
4. **rank (uat)** — order AI-doable-with-fewest-questions first (so an AI-doable task that
needs ≤1 question to start floats to the top); human-residual tasks last.
5. **persist (uat)** — write each ranked task as a `brain/suggestions/uat-<project>-<slug>.md`
atom (same dir as suggestion atoms, distinct `uat-` prefix + `mode: uat` discriminator;
atomic write, update-not-duplicate). These atoms carry NO `scores` field — the triage
(`ai_doable` + `clarifying_question` + `human_residual_steps`) IS the score.
## How to read the output
The output view depends on the mode.
### `--small` / `--big` (suggestion view)
The CLI prints, in order:
- **Grounding confirmation** — a line confirming untrusted-data framing was applied:
`Grounding: all fetched content (CLAUDE.md, issues, atoms) treated as untrusted data.`
- **Ranked shortlist** — one line per surviving candidate: rank, composite score, title,
anchor, and the four per-axis scores. Top of the list = best per the rubric.
- **Discrimination flag** — `OK` means the composite scores have real spread (the
ranking is meaningful). `WARN — NOT discriminating` means the rubric scored everything
the same; treat the ranking as LOW-CONFIDENCE and re-run or inspect the candidates
manually before acting on the order.
- **Dropped count** — how many candidates the partial-verdict guard dropped (a judge
call returned malformed output for them). A high count means the judge calls are flaky.
- **Persisted atom paths** — one line per atom written: `brain/suggestions/sugg-<slug>.md (mode: small|big, status: proposed)`.
### `--uat` (triage view)
A `--uat` run prints the UAT triage shortlist instead (no discrimination line — the UAT
triage is not a 4-axis rubric). Per ranked task, in AI-doable-with-fewest-questions-first
order:
- A verdict line: `ai_doable=yes|no`, the consensus question-count (`none` / `one` /
`many`), and the `anchor`, followed by the task `description`.
- Then ONE actionable line keyed to the triage:
- **AI-doable AND exactly one consensus question** → the single clarifying question an
AI needs answered to START the work.
- **NOT AI-doable** → the exact `human_residual_steps` the human must still do (or
"(none listed)").
- (An AI-doable task with `none` / `many` questions surfaces neither — just the verdict
line: a single question is not the actionable next step there.)
- **Dropped count** — how many tasks the partial-verdict guard dropped.
- **Persisted atom paths** — the `brain/suggestions/uat-*.md` files written.
## Where atoms are persisted
All atoms land in `brain/suggestions/`, one git-tracked markdown+YAML file each (the
dateless id means a re-run updates in place). The filename prefix is mode-discriminated:
- `--small` / `--big` → `sugg-<project>-<slug>.md` (slug from the candidate title).
- `--uat` → `uat-<project>-<slug>.md` (slug from the task `description`).
Both kinds share the directory but never collide — the distinct `sugg-` / `uat-` prefix
keeps the readers isolated. The frontmatter spec is in `brain/SCHEMA.md` (mirrors
`src/goblin/models.py`). A suggestion atom carries `status: proposed`, `type: suggestion`,
and the `mode` it was produced under (`small` / `big`); a UAT atom carries
`status: proposed`, `mode: uat`, and its triage fields (no `scores`).
## Next step
To act on a persisted atom:
```text
/goblin-do <id>
```
`/goblin-do` is the single front door for a resolved atom: a `small` suggestion or safe
`uat` task is EXECUTED via /build-step; a `big` suggestion or not-safe task is HANDED OFF
(it prints the `/plan-feature` seed). The id is the atom filename stem, e.g.
`sugg-toybox-add-a-smoke-test`. (`goblin handoff` survives only as a deprecated CLI alias —
see [Relationship to other skills](../goblin-do/SKILL.md#relationship-to-other-skills).)