/hard-cheese
The gate mitigates epistemic debt — the failure mode where AI-scaffolded code passes review, type-checks, and tests green while the author cannot explain it to a reviewer.
Inputs
text
/hard-cheese [<slug>] [--socratic-cap N=3] [--passing-score N=3] [--no-judge]
Arguments:
- — optional. Identifies the artifact at
.cheese/hard-cheese/<slug>.md
. When omitted, fall back to the git short SHA of . An explicit slug always wins.
- — max retry attempts before the gate marks the artifact and exits non-zero. Default . Vibecheck does not cap; easy-cheese does to avoid infinite loops.
- — minimum SOLO score that counts as PASS. Valid range ; default (Multistructural-or-higher). A previous PASS below the requested threshold is treated as stale and must be re-judged.
- — log-only mode. Capture the user's explanation, write the artifact with , skip the judge sub-agent spawn. Mirrors vibecheck's optional JSONL telemetry mode.
Invocation modes
| Mode | How it fires | Where the gate sits |
|---|
| standalone | User runs directly before opening a pull request. | Outside the pipeline. No upstream skill required. |
| propagated | invokes after its final writing gate and before publication. | At the verified-artifacts → share-for-review boundary. |
propagates through
/cheese → /mold → /cook → /press → /age → /cure → /plate
. Upstream skills pass the flag;
is the only pipeline skill that invokes
.
Portability reference:
../cheese/references/harness-portability.md
. It covers helper resolution, sub-agent dispatch, GitHub operations, and handoff transitions; prefer the bundled or repo-local helper first, and treat
as optional host-provided fallback.
The handoff blocks below are the portable contract; slash commands are host renderings, not the control model.
Flow
-
Resolve scope.
- ,
diff_head = <short-sha of HEAD>
.
- If exists, load it as the intent reference (optional — diff is the ground truth).
- Slug fallback when none supplied: the HEAD short SHA.
- If the working tree has no diff against , exit with and write no artifact.
-
Freshness check.
Check freshness before launching the gate:
python3 skills/hard-cheese/scripts/hard-cheese.pyz freshness-check \
--slug <slug> --passing-score <n>
Exit 0 (
): print
and exit
. Exit 2 (
: HEAD moved or the last PASS score is below
) or 3 (
): continue to step 3.
-
Compose the vibecheck prompt (faithful to Sankaranarayanan 2026, generalised to "share for review" so the gate stays implementation-agnostic):
Before this is shared for review, explain its causal logic in your own words. How does <feature or fix> work? Why does it produce the desired behavior? What state, control flow, or invariants does it rely on?
Render a diff summary alongside the prompt. When invoked by
, also render
's final artifact inventory and
{target, backend, verified}
rows so the explanation covers the exact state about to be shared.
-
Capture the user's explanation as free text. No coaching, no example answers — the explanation is the artifact under test.
-
Spawn the judge sub-agent in fresh context (same pattern
's fan pathway uses for adversarial review). The judge:
- Reads
references/judge-prompt.md
as its system prompt.
- Receives the passing score threshold, the diff summary, the spec excerpt (if any), and the user's explanation as context.
- Returns a JSON object:
{score, level, pass, feedback, socratic_qs}
.
See
references/judge-prompt.md
for the full system prompt and output shape.
Skip this step when
is set: mark the attempt
, write the artifact, exit
.
-
On judge result:
- → PASS. → FAIL, render Socratic questions, loop to step 4 if
attempts < --socratic-cap
. Judge error → ERROR attempt, print warning, exit (fail-open — see ## Divergence from the paper
).
Append the attempt row:
python3 skills/hard-cheese/scripts/hard-cheese.pyz append-attempt \
--slug <slug> --status <PASS|FAIL|ERROR> --score <n> \
--feedback "<judge feedback>" --explanation "<user explanation>"
-
On cap exhaustion: set the artifact
, print the path, exit non-zero. Downstream chains must not proceed.
Artifact
.cheese/hard-cheese/<slug>.md
is the audit trail. The directory is gitignored by repo convention (
already ignores
), so the trail stays local — matching vibecheck's local-only stance on telemetry.
Each file opens with a YAML frontmatter block that travels with the audit trail:
yaml
---
slug: <slug>
attribution: Sankaranarayanan 2026 / vibecheck
rubric: SOLO Taxonomy (1-5), pass threshold = <passing-score>
passing_score: <n>
divergence: fail-open on judge error (vibecheck fails closed)
diff_base: <sha>
diff_head: <short-sha>
status: PASS | FAIL | FAILED | LOGGED
attempts: <n>
---
The attempt log uses a 6-column markdown table (written by
):
markdown
| --- | --- | --- | --- | --- | --- |
| 2026-06-25T10:00:00+00:00 | a1b2c3d | FAIL | 2 | "Unistructural: lists steps but no causal link" | <user explanation verbatim> |
| 2026-06-25T10:05:00+00:00 | a1b2c3d | PASS | 4 | "Relational: explains why invariant holds" | <user explanation verbatim> |
Attempts append; nothing is overwritten within a single invocation. If a re-invocation finds the artifact stale (HEAD moved), new attempt rows are appended below the prior ones — the trail is cumulative.
Sub-agent contract — fresh judge
- Fresh context, every invocation. Same-context judging is biased toward the code it helped write.
- Resolve a no-tool or read-only through the shared agent resolver at power and effort. A general worker qualifies only with prompt-only no-write enforcement and .
references/judge-prompt.md
is the system prompt. The judge reads the supplied diff summary, spec excerpt, and explanation, then returns JSON without repository writes.
- JSON output is parsed. If parsing fails, the attempt is logged as and the gate fails open (see
## Divergence from the paper
).
If the host harness has no sub-agent primitive,
is the wrong skill — the gate cannot run without a fresh judge. Recommend
for users who still want the explanation captured as telemetry without the grading step.
Attribution
Sankaranarayanan, S. (2026).
Mitigating 'Epistemic Debt' in Generative AI-Scaffolded Novice Programming using Metacognitive Scripts. Proceedings of the 13th ACM Conference on Learning at Scale.
https://arxiv.org/abs/2602.20206
The implementation reference (intercept-at-acceptance, SOLO rubric, Socratic retry) is the open-source VS Code extension by the paper's author:
The attribution appears in this
, in
references/judge-prompt.md
, and in every
.cheese/hard-cheese/<slug>.md
artifact so the citation travels with the audit trail.
Divergence from the paper
Hard-cheese departs from vibecheck in exactly one place, and the divergence is called out explicitly so it stays legible:
Vibecheck fails closed on judge error. If the Judge LLM cannot produce a verdict, the modal blocks code application until the judge recovers or the user retries with a different model.
Hard-cheese fails open on judge error. If the fresh-context judge sub-agent crashes, times out, or returns malformed JSON, the gate writes an
attempt, prints a clear warning, and exits
— the user is allowed to proceed.
Rationale: judge invocation is per-PR-attempt and per-retry, and a strict fail-closed policy creates a worse experience under API hiccups than the epistemic-debt cost it averts. New divergences must be added here.
Composition with
and
may coexist. The gate punctures auto exactly once inside terminal
, after
verifies final artifacts and before publication. The user responds, then PASS permits publication, FAILED halts, and ERROR follows the documented fail-open behavior.
Commit-only
does not fire because nothing is shared. For a new PR under auto,
honors explicit topology, infers an obviously cohesive single, and asks when stacked is recommended or shape is ambiguous. Non-TTY behavior lives in
references/composition.md
.
Output
When the gate ends, print:
Hard-cheese artifact: .cheese/hard-cheese/<slug>.md
Status: PASS | FAILED | LOGGED | ERROR
Attempts: <n>
Followed by:
- On PASS:
Ready to share for review.
- On FAILED:
Cap exhausted. Improve understanding of the change before sharing.
- On LOGGED:
Telemetry only — judge skipped via --no-judge.
- On ERROR: a one-line warning naming the failure mode and
Fail-open divergence active — gate exited 0; you may share for review at your discretion.
Preferred tools and fallbacks
| Need | Prefer | Fallback |
|---|
| Diff inspection for the user-facing summary | | |
| Reading the spec (when present) | bounded file read per code-intelligence-routing.md
| host file read |
| Spawning the judge | host sub-agent primitive ( or harness equivalent) | none — without sub-agent spawn, run mode and tell the user the judge is unavailable |
| GitHub / PR context (out of scope here) | n/a | n/a |
Rules
- The judge sub-agent runs in fresh context. Do not let the same conversation that wrote the code grade the human's understanding of it.
- Do not coach the user before they answer. The explanation is the artifact under test. Socratic questions appear only after a FAIL, and only the questions returned by the judge — no extra hints from the parent.
- Do not paraphrase the user's explanation before passing it to the judge. The judge grades what the user wrote, verbatim.
- Do not skip the freshness check. Re-invoking after HEAD has moved must trigger a fresh attempt sequence — prior comprehension is stale once the code changes.
- Do not silently drop ERROR attempts. The fail-open divergence requires that every judge failure is recorded in the artifact and surfaced to the user as a warning.
- Do not invoke or any specific PR-creation tool. The gate's contract is "before code is shared for review" — implementation-agnostic.
- Apply the shared voice kernel (lives at
../age/references/voice.md
): say what the gate result was, flag residual risk as certain | speculating | don't know
, do not soften FAILED into "almost passing".
References
references/judge-prompt.md
— SOLO Taxonomy rubric, judge sub-agent system prompt, JSON output shape.
references/composition.md
— the full / matrix and the single puncture point.
skills/hard-cheese/scripts/hard-cheese.pyz freshness-check
— checks whether a previous PASS is still fresh for the current HEAD and passing score (step 2).
skills/hard-cheese/scripts/hard-cheese.pyz append-attempt
— atomically appends an attempt row to the audit trail (step 6).
Agent resolution
Resolve the fresh judge through
../cheese/references/agent-resolution.md
.
| Work | Preferred types | Permissions/isolation | Minimum power | Effort | Fallback |
|---|
| Grade the explanation | reviewer | no-tool or read-only, fresh-context | default | high | compatible reviewer, then general |
The canonical hard-cheese audit carries the shared
block.