hard-cheese · diff

git:20260809.4871d45 to git:20260905.0a0b1f7

88 added, 63 removed. Audit A to A.

---
name: hard-cheese
- description: Metacognitive vibecheck gate before code is shared for review — make the author explain the diff's causal logic, graded by a fresh-context judge against the SOLO Taxonomy. Use when the user wants this gate — phrases like "/hard-cheese", "/cheese --hard", "gate this before I push", "vibecheck me", "make sure I understand this diff", "epistemic-debt check". Use standalone before opening a PR, or as the `--hard` flag propagated through the pipeline. Do NOT use for code review (`/age`), test hardening (`/press`), or fix application (`/cure`).
+ description: Checks whether an author can explain a code change before review. Use when the user requests `/hard-cheese`, `/cheese --hard`, or an understanding check. Use it before a pull request or through the `--hard` pipeline flag. Do not use it for reviews, test hardening, or fixes.
license: MIT
metadata: {dispatches-agents: true}
---
# /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.
+ The gate reduces **epistemic debt**. This debt exists when code passes checks, but the author cannot explain it.
## Inputs
```text
/hard-cheese [<slug>] [--socratic-cap N=3] [--passing-score N=3] [--no-judge]
```
Arguments:
- - `<slug>` — optional. Identifies the artifact at `.cheese/hard-cheese/<slug>.md`. When omitted, fall back to the git short SHA of `HEAD`. An explicit slug always wins.
- - `--socratic-cap N` — max retry attempts before the gate marks the artifact `FAILED` and exits non-zero. Default `3`. Vibecheck does not cap; easy-cheese does to avoid infinite loops.
- - `--passing-score N` — minimum SOLO score that counts as PASS. Valid range `1..5`; default `3` (Multistructural-or-higher). A previous PASS below the requested threshold is treated as stale and must be re-judged.
- - `--no-judge` — log-only mode. Capture the user's explanation, write the artifact with `status: LOGGED`, skip the judge sub-agent spawn. Mirrors vibecheck's optional JSONL telemetry mode.
+ - `<slug>` identifies the artifact at `.cheese/hard-cheese/<slug>.md`. This argument is optional. Without it, use the short SHA of `HEAD`. An explicit slug overrides the SHA.
+ - `--socratic-cap N` sets the maximum number of retries. The gate then marks the artifact `FAILED` and returns a non-zero status. The default is `3`. Vibecheck has no limit, but easy-cheese prevents infinite loops.
+ - `--passing-score N` sets the minimum SOLO score for PASS. Use a value from `1` through `5`. The default is `3`. The gate treats a previous PASS below this value as stale.
+ - `--no-judge` enables log-only mode. Record the user's explanation with `status: LOGGED`. Do not start the judge sub-agent. This mode is the easy-cheese equivalent of the optional JSONL telemetry mode in vibecheck. It retains more content. See `## Divergence from the paper`.
## Invocation modes
- | Mode | How it fires | Where the gate sits |
+ | Mode | How the gate runs | Where the gate sits |
| --- | --- | --- |
- | **standalone** | User runs `/hard-cheese <slug>` directly before opening a pull request. | Outside the pipeline. No upstream skill required. |
- | **propagated** | `/plate --hard` invokes `/hard-cheese <slug>` after its final writing gate and before publication. | At the verified-artifacts → share-for-review boundary. |
+ | **standalone** | The user runs `/hard-cheese <slug>` before a pull request. | Outside the pipeline. No upstream skill is required. |
+ | **propagated** | `/plate --hard` runs `/hard-cheese <slug>` after the final writes and before publication. | At the verified-artifacts to share-for-review boundary. |
- `--hard` propagates through `/cheese → /mold → /cook → /press → /age → /cure → /plate`. Upstream skills pass the flag; `/plate` is the only pipeline skill that invokes `/hard-cheese`.
+ `--hard` passes through `/cheese → /mold → /cook → /press → /age → /cure → /plate`. Only `/plate` runs `/hard-cheese`.
- Portability reference: [`../cheese/references/harness-portability.md`](../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 `${CLAUDE_SKILL_DIR}` as optional host-provided fallback.
- The handoff blocks below are the portable contract; slash commands are host renderings, not the control model.
+ See [`../cheese/references/harness-portability.md`](../cheese/references/harness-portability.md) for portability requirements. It covers helper resolution, sub-agent dispatch, GitHub operations, and handoff transitions.
+ Use the bundle or repository helper first. Use `${CLAUDE_SKILL_DIR}` only as an optional host fallback.
+ The handoff blocks define the portable contract because slash commands are host renderings, not the control model.
## Flow
1. **Resolve scope.**
- - `diff_base = origin/main`, `diff_head = <short-sha of HEAD>`.
- - If `.cheese/specs/<slug>.md` 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 `origin/main`, exit `0` with `"nothing to gate on"` and write no artifact.
+ - Set `diff_base = origin/main` and `diff_head = <short-sha of HEAD>`.
+ - Load `.cheese/specs/<slug>.md` as the optional intent reference when it exists. The diff remains the source of truth.
+ - Use the short SHA of `HEAD` when no slug exists.
+ - If the diff against `origin/main` is empty, return `0` with `"nothing to gate on"`. Do not write an artifact.
2. **Freshness check.**
- Check freshness before launching the gate:
+ Run the freshness check before you run the gate:
```
python3 skills/hard-cheese/scripts/hard-cheese.pyz freshness-check \
--slug <slug> --passing-score <n>
```
- Exit 0 (`previously_passed`): print `"previously passed"` and exit `0`. Exit 2 (`stale`: HEAD moved or the last PASS score is below `--passing-score`) or 3 (`new`): continue to step 3.
+ Exit `0` for `previously_passed`. Print `"previously passed"` and stop. Continue to step 3 for `stale` or `new`.
+ A stale result has exit status `2`. A new result has exit status `3`.
+ A result is stale when `HEAD` changes or the last PASS score is too low.
- 3. **Compose the vibecheck prompt** (faithful to Sankaranarayanan 2026, generalised to "share for review" so the gate stays implementation-agnostic):
+ 3. **Compose the vibecheck prompt.** Keep it faithful to Sankaranarayanan 2026. Use "share for review" to keep the gate implementation independent.
> 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 `/plate`, also render `/plate`'s final artifact inventory and `{target, backend, verified}` rows so the explanation covers the exact state about to be shared.
+ Show a diff summary with the prompt. For `/plate`, also show the complete final evidence:
- 4. **Capture the user's explanation** as free text. No coaching, no example answers — the explanation is the artifact under test.
+ - the final artifact inventory,
+ - each `{target, backend, verified}` completion row,
+ - the tracked artifact diff,
+ - the quality gate result.
- 5. **Spawn the judge sub-agent** in fresh context (same pattern `/cook`'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}`.
+ Stop with a non-zero status when `/plate` omits one of these four values. Stop with a non-zero status when a completion row has `verified: false`.
+ 4. **Record the user's explanation** as free text. Do not provide coaching or example answers. The explanation is the artifact under test.
+
+ 5. **Start the judge sub-agent** in a fresh context. Use the same pattern as the `/cook` fan pathway.
+ - Use `references/judge-prompt.md` as the system prompt.
+ - Provide the passing score, diff summary, optional spec excerpt, and user's explanation.
+ - Require this 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 `--no-judge` is set: mark the attempt `status: LOGGED`, write the artifact, exit `0`.
+ Skip this step when the user sets `--no-judge`. Mark the attempt `status: LOGGED`, write the artifact, and return `0`.
- 6. **On judge result:**
- - `score >= <passing-score>` → PASS. `score < <passing-score>` → FAIL, render Socratic questions, loop to step 4 if `attempts < --socratic-cap`. Judge error → ERROR attempt, print warning, exit `0` (fail-open — see `## Divergence from the paper`).
+ 6. **Process the judge result.**
+ - Mark the attempt PASS when `score >= <passing-score>`.
+ - Mark the attempt FAIL when `score < <passing-score>`. Show the Socratic questions. Return to step 4 while retries remain.
+ - Mark the attempt ERROR when the judge fails. Print a warning and return `0`. 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>"
```
- 7. **On cap exhaustion:** set the artifact `status: FAILED`, print the path, exit non-zero. Downstream chains must not proceed.
+ 7. **Process an exhausted limit.** Set the artifact `status: FAILED`. Print the artifact path and return a non-zero status. Stop downstream chains.
## Artifact
- `.cheese/hard-cheese/<slug>.md` is the audit trail. The directory is gitignored by repo convention (`.gitignore` already ignores `.cheese/`), so the trail stays local — matching vibecheck's local-only stance on telemetry.
+ `.cheese/hard-cheese/<slug>.md` contains the audit trail. The `.gitignore` file excludes `.cheese/`, so the audit trail remains local.
- Each file opens with a YAML frontmatter block that travels with the audit trail:
+ Each file starts with this YAML frontmatter block:
```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
+ status: PASS | FAIL | FAILED | LOGGED | ERROR
attempts: <n>
---
```
- The attempt log uses a 6-column markdown table (written by `append-attempt`):
+ `append-attempt` writes the attempt log as this six-column markdown table:
```markdown
| timestamp | head_sha | status | score | feedback | explanation |
| --- | --- | --- | --- | --- | --- |
| 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.
+ Each invocation appends attempts and does not overwrite rows. If `HEAD` changes, append new rows below the earlier rows.
## 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 `reviewer` through the shared agent resolver at `default` power and `high` effort. A general worker qualifies only with prompt-only no-write enforcement and `degraded: true`.
- - `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 `ERROR` and the gate fails open (see `## Divergence from the paper`).
+ - **Use fresh context for every invocation.** The code-writing context can bias the judge.
+ - Resolve a no-tool or read-only `reviewer` at `powerful` power and `high` effort. Use the shared agent resolver.
+ - The shared resolver pins each reviewer to `powerful`. Do not lower this value for the judge.
+ - Use a general worker only with no-write enforcement. Set `degraded: true`.
+ - Use `references/judge-prompt.md` as the system prompt.
+ - Give the judge the diff summary, the optional spec excerpt, and the explanation. Require a JSON reply. Prohibit repository writes.
+ - **Parse the JSON output.** On a parse error, log an `ERROR` attempt and fail open.
- If the host harness has no sub-agent primitive, `/hard-cheese` is the wrong skill — the gate cannot run without a fresh judge. Recommend `/hard-cheese --no-judge` for users who still want the explanation captured as telemetry without the grading step.
+ The gate requires a host sub-agent feature. Without this feature, recommend `/hard-cheese --no-judge` to record the explanation without a grade.
## 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 implementation uses the open-source VS Code extension from the paper's author:
<https://github.com/sreecharansankaranarayanan/vibecheck>
- The attribution appears in this `SKILL.md`, in `references/judge-prompt.md`, and in every `.cheese/hard-cheese/<slug>.md` artifact so the citation travels with the audit trail.
+ This `SKILL.md`, `references/judge-prompt.md`, and each artifact include the attribution. Thus, the citation stays 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:
+ Hard-cheese has two differences from vibecheck:
- **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.
+ **1. Judge errors.** Vibecheck fails closed. The modal blocks code application until the judge recovers or the user retries.
- **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 `ERROR` attempt, prints a clear warning, and exits `0` — the user is allowed to proceed.
+ Hard-cheese fails open. The gate records an `ERROR`, prints a warning, and returns `0`.
- 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.
+ This policy prevents API failures from blocking pull request work.
+ **2. Telemetry content.** Vibecheck records the length of an explanation. It never records the text of an explanation.
+
+ Hard-cheese records the complete text of every explanation in the local artifact. The `.gitignore` file excludes `.cheese/`, so this text remains on the author's machine.
+
+ Tell the user about this retention before `--no-judge` records the first explanation.
+
+ Add each new difference to this section.
+
## Composition with `--auto`
- `--hard` and `--auto` may coexist. The gate punctures auto exactly once inside terminal `/plate --hard`, after `/plate` verifies final artifacts and before publication. The user responds, then PASS permits publication, FAILED halts, and ERROR follows the documented fail-open behavior.
+ `--hard` and `--auto` can operate together. Terminal `/plate --hard` pauses automation once before publication, after `/plate` verifies the final artifacts.
+ The user responds to the prompt. PASS permits publication. FAILED stops publication. ERROR uses the documented fail-open behavior.
- Commit-only `/plate --hard` does not fire because nothing is shared. For a new PR under auto, `/plate` 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`.
+ Commit-only `/plate --hard` does not run the gate. That path shares nothing. See `references/composition.md` for new pull requests and non-TTY behavior.
## Output
When the gate ends, print:
```
Hard-cheese artifact: .cheese/hard-cheese/<slug>.md
Status: PASS | FAILED | LOGGED | ERROR
Score: <n>/5 (<SOLO level>, pass ≥ <passing-score>)
Attempts: <n>
```
- The `Score` line reports the latest judged attempt. Omit it when no attempt was judged — LOGGED mode, or an ERROR run with no scored attempts.
+ The `Score` line reports the latest judged attempt. Omit this line for LOGGED mode or an ERROR without a scored attempt.
- Followed by:
+ Then print one applicable message:
- 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.`
+ - On ERROR: Print one warning that identifies the failure. Include `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 | `delta` | `git diff --unified=3` |
| Reading the spec (when present) | bounded file read per [`code-intelligence-routing.md`](../cheese/references/code-intelligence-routing.md) | host file read |
| Spawning the judge | host sub-agent primitive (`Agent()` or harness equivalent) | none — without sub-agent spawn, run `--no-judge` 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 `/gh` 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".
+ - Run the judge sub-agent in fresh context. Do not use the code-writing context to grade the author's understanding.
+ - Do not coach the user before the answer. The explanation is the artifact under test.
+ - Show only the judge's Socratic questions after a FAIL. Do not add hints.
+ - Pass the user's explanation to the judge unchanged.
+ - Always run the freshness check. A changed `HEAD` requires a new attempt sequence.
+ - Record every ERROR attempt. Show a warning for each judge failure.
+ - Do not call `/gh` or a specific pull request tool. The gate operates before code enters review.
+ - Apply the shared voice rules from `../age/references/voice.md`. Report the result. Classify the residual risk as `certain | speculating | don't know`.
+ - Do not describe FAILED as `"almost passing"`.
## References
- - `references/judge-prompt.md` — SOLO Taxonomy rubric, judge sub-agent system prompt, JSON output shape.
- - `references/composition.md` — the full `--hard` / `--auto` 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).
+ - `references/judge-prompt.md` defines the SOLO Taxonomy rubric, judge prompt, and JSON output.
+ - `references/composition.md` defines the complete `--hard` and `--auto` matrix.
+ - [`references/commands.md`](references/commands.md) lists the generated bundle commands.
## Agent resolution
Resolve the fresh judge through [`../cheese/references/agent-resolution.md`](../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 |
+ | Grade the explanation | reviewer | no-tool or read-only, fresh-context | powerful | high | compatible reviewer, then general |
- The canonical hard-cheese audit carries the shared `agent_resolution` block.
+ The canonical hard-cheese audit includes the shared `agent_resolution` block.