Skip to content

/goal-judge

The gate. Reviews the active goalkeeper goal against its definition-of-done and either approves (advance / mark done) or rejects (with a structured fix-list). Auto-fired by the goal skill when the validator passes (inline mode), or invoked by the goal-chain orchestrator after

shell
$ npx -y skills add bonfire-systems/goalkeeper --skill goal-judge --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/goal-judge
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

The gate. Reviews the active goalkeeper goal against its definition-of-done and either approves (advance / mark done) or rejects (with a structured fix-list). Auto-fired by the goal skill when the validator passes (inline mode), or invoked by the goal-chain orchestrator after

SKILL.md

goal-judge.SKILL.md
name: goal-judge
description: The gate. Reviews the active goalkeeper goal against its definition-of-done and either approves (advance / mark done) or rejects (with a structured fix-list). Auto-fired by the goal skill when the validator passes (inline mode), or invoked by the goal-chain orchestrator after executor subagent returns (subagent mode, v0.3+). Can also be invoked on demand via /goal-judge for advisory review.

You are operating the **goal-judge** skill — the gate that decides whether a goal is actually done, not just superficially passing the validator. The judge is what differentiates goalkeeper from a naive auto-loop.

Invocation sources (v0.3+)

The judge is invoked from one of three places:

1. **Inline mode** — `/goal` skill's execution loop auto-fires the judge when the validator passes (the historical default). 2. **Subagent mode** — `/goal-chain` orchestrator invokes the judge AFTER the executor subagent returns with `STATUS: validator_pass`. The executor never invokes the judge itself; that responsibility moved up to the chain orchestrator in v0.3. 3. **Advisory on-demand** — user runs `/goal-judge` directly for a non-binding read on an in-progress goal (does not advance state).

The verdict logic and grading rubric are identical across all three sources. Only the invocation context differs.

Inputs

  • `.claude/goals/active.json` → `<slug>`
  • `.claude/goals/<slug>/contract.md` (especially `definition_of_done`)
  • `.claude/goals/<slug>/log.md` (the full progress log)
  • `state.started_at_commit` — git baseline captured at activation; use as the diff origin
  • `state.started_at_dirty_paths` — paths that were already dirty at activation; the judge should NOT credit/blame those
  • `state.validator_baseline_result` — `"pass" | "fail" | "not_runnable" | null` — was the validator passing at activation? Captured by `/goal-prep`.
  • `state.validator_baseline_failing_paths` — paths the validator flagged at baseline; if the final validator failure is on these same paths, it's pre-existing dirt, not goal-caused
  • `args` — optional: `--mode=inline|subagent` to override `judge_mode` from contract
  • **Subagent-mode extra context** — when invoked by `/goal-chain` after executor return, the orchestrator passes the executor's structured summary (STATUS, SUMMARY, VALIDATOR_OUTPUT_TAIL, FILES_CHANGED, BLOCKERS) as additional context. The judge uses this as a leading hint but MUST still independently verify against the contract — the executor's self-report is not authoritative.

Build the judge prompt — mechanical assembly

Don't improvise this. Each judge invocation must produce the same prompt-shape so verdicts are comparable across runs.

Step 1 — read state

slug         = <read .claude/goals/active.json>.slug
state        = <read .claude/goals/<slug>/state.json>
contract_md  = <read .claude/goals/<slug>/contract.md verbatim>
log_md       = <read .claude/goals/<slug>/log.md verbatim>

Step 2 — assemble the exclusion pathspecs

Default exclusions (always apply):

DEFAULT_EXCLUDES=(
  ':!package-lock.json' ':!yarn.lock' ':!pnpm-lock.yaml'
  ':!Cargo.lock' ':!poetry.lock' ':!go.sum'
  ':!Gemfile.lock' ':!composer.lock'
  ':!dist/**' ':!build/**' ':!out/**' ':!target/**' ':!.next/**'
  ':!**/*.min.js' ':!**/*.min.css'
  ':!coverage/**' ':!.nyc_output/**' ':!test-results/**'
  ':!.vscode/**' ':!.idea/**' ':!.DS_Store'
)

Append contract `diff_excludes` if present (each entry becomes `:!<glob>`).

If contract has `diff_includes` (rare narrowing), use those positively instead of default-minus-excludes — e.g. `git diff <baseline>..HEAD -- packages/api/ packages/web/`.

Step 3 — compute the diff

If `state.started_at_commit` is non-null (git repo):

# Committed work since baseline
git diff <state.started_at_commit>..HEAD -- "${DEFAULT_EXCLUDES[@]}" <user_excludes...>

# Uncommitted working-tree work (staged + unstaged)
git diff -- "${DEFAULT_EXCLUDES[@]}" <user_excludes...>

# Untracked new files (not shown by git diff)
git ls-files --others --exclude-standard -- "${DEFAULT_EXCLUDES[@]}"

Concatenate the three outputs in that order. For untracked new files, also Read them so the judge sees their full content (not just the path list).

If `state.started_at_commit` is null (not a git repo): use `git status` if available; otherwise note "no-git — review log + files only" in the prompt.

Step 4 — compute the file list

# Modified files (committed + uncommitted)
git diff --name-only <state.started_at_commit>..HEAD -- "${DEFAULT_EXCLUDES[@]}" <user_excludes...>
git diff --name-only -- "${DEFAULT_EXCLUDES[@]}" <user_excludes...>

# Untracked
git ls-files --others --exclude-standard -- "${DEFAULT_EXCLUDES[@]}"

Dedupe and absolutize (prefix with the repo root). This is the file list the judge subagent must Read end-to-end.

Step 5 — pre-existing-dirt subtraction

For each path in `state.started_at_dirty_paths`: if the path also appears in step 4's file list, mark it for the judge as "pre-existing — verify these changes belong to the goal." Do NOT remove it from the file list (the judge still inspects it), just flag it. The judge's `Pre-existing-dirt check` verdict line addresses this set explicitly.

Step 6 — pre-existing validator-failure subtraction

If `state.validator_baseline_result == "fail"`, the validator was ALREADY failing at activation. Capture the current validator failure paths and compare:

  • **Goal-caused failure:** current failing path is NOT in `state.validator_baseline_failing_paths`, OR `state.validator_baseline_result` was `"pass"`. → blocks approval.
  • **Pre-existing failure:** current failing path IS in `state.validator_baseline_failing_paths` AND the goal did not modify it (not in step 4 file list). → does NOT block approval if all DoD items are otherwise met. Surface in NOTES so the user can decide whether to fix opportunistically.

When `validator_baseline_result == null` (prep didn't run the validator

Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withgoalkeeper

Durable contract-driven goal execution for Claude Code. A subagent judge gates completion against an explicit Definition of Done.

Get the whole plugin, auto-invoked
Stats
12
Stars
0
Views
2
Forks
Maintained
Maintenance
Python
Language
MIT
License
1mo ago
Last commit
2mo ago
Created

Repo: bonfire-systems/goalkeeper