/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
$ npx -y skills add bonfire-systems/goalkeeper --skill goal-judge --agent claude-codeHow 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
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.mdname: 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
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
Showing the first part of this file.
Durable contract-driven goal execution for Claude Code. A subagent judge gates completion against an explicit Definition of Done.
Repo: bonfire-systems/goalkeeper
Other skills on goalkeeper.
- /goal-chain
Run a linear sequence of goalkeeper goals where the judge gates progression between them. Use when the user invokes /goal-chain "<file>" to start a chain. Also auto-invoked by the judge skill on approval to advance the chain cursor.
Open skill - /goal-clear
Stop and archive the currently active goalkeeper goal. Use when the user invokes /goal-clear to abandon or finalize a goal. Files are moved to .claude/goals/_archive/, never deleted.
Open skill - /goal-pause
Pause the currently active goalkeeper goal without losing state. Use when the user invokes /goal-pause. The goal can later be resumed with /goal-resume.
Open skill - /goal-prep
Interactively draft a goalkeeper contract before executing. Use this skill when the user invokes /goal-prep "<rough idea>", or when /goal is called for a slug that has no contract yet. Produces a well-formed contract.md with explicit objective, definition-of-done, validator
Open skill - /goal-resume
Resume a paused or needs_human goalkeeper goal. Use when the user invokes /goal-resume after they've manually unblocked the goal (e.g. fixed a problem the judge flagged).
Open skill - /goal-supervisor
The mission-level supervisor. One level above goals. Reads the user's mission charter and the most-recently-completed goal's artifacts, then decides whether to PROCEED (draft + activate the next goal), declare the mission DONE, or ESCALATE to the user. Use this skill when the
Open skill

