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:
- Inline mode —
/goalskill's execution loop auto-fires the judge when the validator passes (the historical default). - Subagent mode —
/goal-chainorchestrator invokes the judge AFTER the executor subagent returns withSTATUS: validator_pass. The executor never invokes the judge itself; that responsibility moved up to the chain orchestrator in v0.3. - Advisory on-demand — user runs
/goal-judgedirectly 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(especiallydefinition_of_done).claude/goals/<slug>/log.md(the full progress log)state.started_at_commit— git baseline captured at activation; use as the diff originstate.started_at_dirty_paths— paths that were already dirty at activation; the judge should NOT credit/blame thosestate.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-causedargs— optional:--mode=inline|subagentto overridejudge_modefrom contract- Subagent-mode extra context — when invoked by
/goal-chainafter 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, ORstate.validator_baseline_resultwas"pass". → blocks approval. - Pre-existing failure: current failing path IS in
state.validator_baseline_failing_pathsAND 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, or the goal was activated without prep), the judge has no baseline to subtract from — treat all validator failures as goal-caused. The user can manually amend state.json if they know better.
Decide execution mode
- Read contract
judge_mode, defaultsubagent. Override with--mode=arg if present. - subagent mode (default for gating): spawn a fresh general-purpose subagent via the Agent tool with a clean context. This is the right mode when the judge is gating chain progression or final completion — independent context catches placeholders and shortcuts the executing agent rationalized away.
- inline mode: do the review directly with current context. Cheaper but biased — only use when explicitly requested for advisory review, never for chain gating.
Subagent mode
Spawn the agent with a self-contained prompt assembled from steps 1-5 above. The subagent has not seen this conversation — give it everything it needs.
The judge subagent must do BOTH of these — diffs lose context (renames, surrounding code, file-level structure):
- Read the diff for an overview of what changed
- Read each modified/added file end-to-end via the Read tool to verify behavior, not just surface
Use this prompt template (fill in the bracketed sections from steps 1-5):
You are an independent judge reviewing a goalkeeper goal. You have not seen the executing agent's reasoning — review the artifacts only.
# Contract
[paste contract.md verbatim]
# Progress log
[paste log.md verbatim]
# Diff scope
Baseline: [started_at_commit short SHA or "no-git"]
Validator baseline: [state.validator_baseline_result or "unknown"]
Pre-existing validator-failing paths (failures on these are NOT goal-caused):
[list from state.validator_baseline_failing_paths, or "none/unknown"]
Default + contract exclusions applied (lockfiles, build outputs, coverage, IDE files).
Pre-existing dirty paths at activation (do NOT credit as goal work, but flag if any goal work touched them):
[list from state.started_at_dirty_paths, or "none"]
# Files modified or added since baseline
[absolute path list, one per line]
# Diff (excerpt)
[paste filtered git diff output, or "No git repo — review log + files only" if not a git repo]
# Your task
**Output the verdict ONCE.** Pre-think your reasoning before producing the structured response. Do not self-correct or revise individual DoD lines mid-response — fina