Add a test suite to t3-hardware-scoring, and fix what it found - #1
Merged
Conversation
The skill had no tests, and both fully worked examples in
t3-classification.md disagreed with the script they document:
- The Gray Zone example expects "Toy + Trash (Gray Zone)". The Litmus
Gate override it relies on (Step 3 rule 2) was documented but never
implemented, so the script returned "Trash (Gray Zone)".
- The Eagle Eye example expects final_label "Tool + Trash". The script
baked the annotation into the label field itself.
A third bug had no example to contradict: the three *-auditor.md rubric
guides show a JSON body with no top-level litmus_gate, while
synthesize_results.py reads exactly that field with a "No" default. An
auditor following the rubric guide verbatim produced a report whose gate
silently read No — one of only four primary-classification conditions,
lost without a warning or a non-zero exit.
Changes:
- tests/ with 79 unit tests, an end-to-end golden case, and a link
checker, wired to CI. Fixtures hold a complete worked audit of a
fictional product; the strongest test verifies every verbatim_evidence
quote actually appears in the brand-blinded source, which turns the
zero-hallucination claim into something checked rather than asserted.
- Implement the Gray Zone gate override and the negative-composite
secondary rule; split final_label (parseable) from display_label
(annotated), which also stops "(Gray Zone)" being dropped whenever
Eagle Eye fired too.
- Missing or malformed litmus_gate/total_score is now a hard error.
- scripts/validate_auditor_json.py replaces the hand-merge prose of Step
3.5: it checks arithmetic, item coverage, the evidence-first rule and
the trigger/critical_issues correspondence, then builds
auditor_reports.json by copying rather than retyping.
- Add the missing references/trash-auditor-template.md, which
trash-auditor.md had pointed at since v2.0.
- Drop crawl_product_info.py in favour of the host agent's web tools,
and drop the now-unused requests/beautifulsoup4 dependencies. The
skill is stdlib-only, so it installs with no Python environment.
- Drop the deprecated peer-review-guide.md, and fix README paths to
tooling that was never committed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four files enumerated the triggers and three of them disagreed:
trash-red-flags.md 14 patterns (the real set)
trash-auditor.md 5 patterns
eagle-eye-validator.md 6 patterns, and omitted Core Flaw —
which trash-auditor.md did list
The Trash Auditor scored against one list while the Eagle Eye Validator
reviewed its work against a different one, so nine patterns could never
be scored and Core Flaw could be scored but never revalidated.
trash-red-flags.md is now the single source of truth: it opens with a
Canonical Trigger Index of all 14 patterns and their items, and the other
copies are regenerated from it. The duplication stays deliberately — an
auditor subagent reads one rubric file and should not need to open a
second to know what fires — so the fix is to pin the copies, not merge
them. test_trigger_consistency.py fails the build when any copy, or the
pattern table in validate_auditor_json.py, drifts from the index.
Verified by mutation: dropping a row, changing an item id, and editing
the code table each fail the suite.
Also fixed along the way:
- A blank line inside the Honesty table split Architectural
Implausibility into its own broken one-row table.
- validate_auditor_json.py now checks that a "Triggered:" reason names
a canonical pattern and lands on that pattern's item, instead of only
checking the item was trigger-capable.
- Three pattern names contain a period ("Price vs. Doubt"), so parsing
the name by splitting the reason on its first period yields
"Price vs". trigger_pattern() matches known names longest-first.
- The golden fixture said "Subscription Trap" where the canonical name
is "Subscription Trap / Brick" — caught by the new test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A sweep over everything the earlier passes had not verified. Suite goes
from 92 to 136 tests, and 20 deliberate mutations are now all caught.
Correctness:
- An empty top-level critical_issues silently shadowed a populated
extract_for_report.critical_issues, turning a vetoed product back
into a clean Tool. This is a safety veto, so where the two copies
disagree it now errs toward firing; the validator flags the
disagreement itself.
- The validator collapsed a duplicated item ID across two sections,
so the arithmetic check passed against the wrong evidence.
- The validator now checks that a "Triggered:" reason names a real
pattern and lands on that pattern's item, not merely that the item
was trigger-capable.
Metadata that did not describe reality:
- report-schema.md never documented display_label, though SKILL.md
Step 6 tells the agent to use it. Added, with gray_zone and the
real confidence values.
- references/defluff-guide.md is titled "Brand Blinding Guide" and its
central rule is "do NOT delete hype" — a filename that instructs the
opposite of the file. Renamed to brand-blinding-guide.md.
- eagle-eye-validator.md never said where Step 4's diffs get applied.
Now states 03-*.json then re-merge, so totals stay derived.
- Added .claude-plugin/marketplace.json, so the plugin install path
the README documents actually resolves. Schema taken from two
working marketplaces rather than guessed, and a test asserts every
listed path exists and every skill on disk is published.
New guards, each verified by mutation:
- Auditor templates are the JSON subagents copy: all three must parse,
carry exactly their rubric's item IDs, agree with their own scoring
table, and declare the fields the scripts read.
- report-schema.md's chart dimension maxima must sum to each role's
total, so they cannot outlive a rubric change.
- Version, skill name, and stdlib-only imports are checked, the last
via AST — a prose line beginning "from a source file" reads as an
import to a grep.
- CLI exit codes for both scripts, including that a refused merge
leaves no partial output and that merge output feeds the synthesizer.
Two earlier tests passed for the wrong reason and were tightened: a
critical_issues assertion that the new top-vs-nested check also
satisfied, and a missing case for a pattern scored on the wrong item.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The skill had no tests. Adding them turned up three bugs that change audit
verdicts, plus a set of documentation that had drifted from the code it
describes.
What the tests found
Both fully worked examples in
t3-classification.mddisagreed with the scriptthey document.
Toy + Trash (Gray Zone)Trash (Gray Zone)final_label: "Tool + Trash"Tool + Trash (Eagle Eye)The Gray Zone case failed because the Litmus Gate override (Step 3 rule 2) was
documented but never implemented, so resolution fell through to the composite
and picked the opposite category.
A missing
litmus_gatewas read as"No", silently. The three*-auditor.mdguides show a JSON body — under a heading reading "MUST FollowThis Structure" — with no top-level
litmus_gate, whilesynthesize_results.pyreads exactly that field with a
"No"default. An auditor following its ownrubric produced a report whose gate silently read No. The gate is one of only
four conditions deciding the primary classification:
An empty
critical_issuesshadowed a populated one. When the top-levelarray was empty but
extract_for_report.critical_issueswas not, the vetosilently did not fire — turning a vetoed product back into a clean Tool. This is
a safety veto, so disagreement between the two copies now errs toward firing it,
and the validator flags the disagreement.
The Eagle Eye trigger set differed across four files.
trash-red-flags.mddefined 14 patterns,
trash-auditor.mdlisted 5, andeagle-eye-validator.mdclaimed "the 6 canonical" triggers while omitting Core Flaw — which the auditor
guide did list. The Trash Auditor scored against one list while the Eagle Eye
Validator reviewed its work against another.
What changed
Tests — 136 of them, wired to CI, stdlib-only so there is no install step.
tests/fixtures/golden-ai-pendant/is a complete worked audit of a fictionalproduct: Brand-Blinded source text, all three auditor reports, and the
classification they synthesize to. It doubles as the reference for what a
finished report looks like — the rubric guides only ever showed fragments with
...placeholders.The strongest test is
test_every_quote_exists_in_the_source_text: every stringin every
verbatim_evidencearray must appear in02-brand-blinded.md. Zerohallucination is the skill's central claim, and this makes it checked rather
than asserted.
Step 3.5 is a script, not prose.
validate_auditor_json.pyreplaces thehand-merge instructions: it checks item coverage, that totals equal the sum of
their items, the evidence-first rule, gate consistency, and that every
"Triggered:"item has a matchingcritical_issuesentry — then buildsauditor_reports.jsonby copying rather than retyping. It refuses to merge onany error.
One canonical trigger list.
trash-red-flags.mdnow opens with a CanonicalTrigger Index; the other copies are pinned to it by
test_trigger_consistency.py. The duplication is deliberate — an auditorsubagent reads one rubric file and should not need to open a second to know what
fires — so the copies are pinned rather than merged.
Web crawling removed.
crawl_product_info.pyusedrequests.getplus regexHTML stripping, which cannot handle JS-rendered pages; Step 1 now uses the host
agent's own web tools. It also declared
beautifulsoup4as a dependency withoutever importing it. The skill is now stdlib-only.
Also fixed: added the missing
references/trash-auditor-template.md, whichtrash-auditor.mdhad pointed at since v2.0; added.claude-plugin/marketplace.jsonso the plugin install path the READMEdocuments actually resolves; documented
display_labelinreport-schema.md;renamed
defluff-guide.md, whose central rule is "do NOT delete hype", tobrand-blinding-guide.md; removed the deprecatedpeer-review-guide.md; andcorrected README references to tooling that was never committed.
final_labelnow stays clean for leaderboard grouping whiledisplay_labelcarries the annotations — which also stops
(Gray Zone)being dropped wheneverEagle Eye fired too.
Verification
20 deliberate mutations were introduced to confirm the suite actually detects
breakage — every threshold, the composite direction, the veto, the gate
override, and each validator check. All 20 are caught. Two tests that initially
passed for the wrong reason were tightened. The suite was re-run from a fresh
clone to confirm it depends on nothing untracked.
🤖 Generated with Claude Code