athola/justify
Audits changes for additive bias and Iron Law compliance. Use when reviewing completed work before merging or after AI-assisted implementation.
npx skills add https://github.com/athola/claude-night-market --skill justify
> The simplest change that fixes the problem is the
> safest change to merge.
> Adding code is easy. Removing the need for code is
> engineering.
AI models are trained to be helpful, which creates
a systematic bias toward *adding* code rather than
*fixing* root causes:
| AI Default Behavior | Correct Behavior |
|---------------------|------------------|
| Add a workaround | Fix the root cause |
| Modify test expectations | Fix the implementation |
| Create a new helper | Use an existing one |
| Add error handling | Prevent the error |
| Add a compatibility shim | Remove the old code |
| Wrap in try/catch | Fix the exception source |
This skill audits changes for these patterns and
requires explicit justification for each.
imbue:karpathy-principles)
imbue:scope-guard)# Determine base branch
base=$(git merge-base master HEAD 2>/dev/null \
|| git merge-base main HEAD 2>/dev/null)
# Get change statistics
git diff "$base" --stat
git diff "$base" --shortstat
git diff "$base" --diff-filter=A --name-only # new files
git diff "$base" --diff-filter=M --name-only # modified files
git diff "$base" --diff-filter=D --name-only # deleted files
Score each dimension 0-3 (0 = clean, 3 = high bias):
| Signal | Weight | How to Measure |
|--------|--------|----------------|
| Line ratio | 2x | additions / max(deletions, 1) |
| New files | 2x | Count of --diff-filter=A |
| Test logic changes | 3x | Test assertion/expectation diffs |
| New abstractions | 1x | New classes, functions, modules |
| Workaround patterns | 2x | Try/catch, if/else guards added |
Line Ratio Scoring:
| Ratio | Score | Interpretation |
|-------|-------|----------------|
| < 2:1 | 0 | Balanced change |
| 2:1 to 5:1 | 1 | Mildly additive |
| 5:1 to 10:1 | 2 | Additive bias likely |
| > 10:1 | 3 | Strong additive bias |
Aggregate Score:
bias_score = sum(signal_score * weight) / sum(weights)
| Aggregate | Zone | Action |
|-----------|------|--------|
| 0.0 - 0.5 | GREEN | Proceed |
| 0.5 - 1.5 | YELLOW | Justify each signal |
| 1.5 - 2.5 | RED | Rethink approach |
| 2.5+ | STOP | Likely wrong approach |
The Iron Law states: tests drive implementation, not
the other way around. Check for violations:
# Find test files that were modified
git diff "$base" --name-only | rg "test_|_test\.|spec\." \
|| git diff "$base" --name-only | grep -E "test_|_test\.|spec\."
# For each modified test file, check what changed
git diff "$base" -- <test_file> | rg "^[-+].*assert|^[-+].*expect|^[-+].*should"
Violation patterns (test logic was tampered):
@skip or @pytest.mark.skip addedEach violation requires explicit justification:
> "I changed this test assertion because the
> *requirement* changed, not because my implementation
> couldn't meet the original requirement."
If the requirement didn't change, the test should not
change. Fix the implementation instead.
For each changed file, answer:
Could the goal be achieved without touching this file?
Could fewer lines achieve the same result?
New functions, classes, or control flow = added
complexity that needs justification.
Could removing code fix the problem instead of
adding code?
Changes can be minimal and still catastrophically wrong
if they silently revise a load-bearing design decision.
For each changed file, check whether it touches a design
invariant:
What counts as an invariant:
separation, data flow direction)
normalized vs denormalized)
actor model, shared-nothing)
Detection heuristic:
# Check for structural changes (new modules, moved
# boundaries, changed interfaces)
git diff "$base" --name-only | rg "(interface|abstract|base|core|types|schema|model)" \
|| git diff "$base" --name-only | grep -E "(interface|abstract|base|core|types|schema|model)"
# Check for pattern-breaking changes
git diff "$base" -U5 | rg "(TODO.*refactor|HACK|WORKAROUND|XXX)" \
|| git diff "$base" -U5 | grep -E "(TODO.*refactor|HACK|WORKAROUND|XXX)"
When an invariant conflict is detected:
Do NOT silently pick a resolution. Present the three
options to the human:
| Option | Description | When Right |
|--------|-------------|------------|
| Preserve | Don't add the feature; the invariant pays dividends | Invariant simplifies many things; feature is marginal |
| Layer | Add feature inelegantly on top | Feature is needed; invariant is still valuable; imperfection is acceptable |
| Revise | Change the invariant itself | Genuine new learning invalidates the original decision |
Add to Justification Report:
### Invariant Impact: NONE / DETECTED
[If DETECTED:]
- **Invariant**: [name the design decision]
- **Conflict**: [what change clashes with it]
- **Option chosen**: Preserve / Layer / Revise
- **Justification**: [why this option, not the others]
- **Human reviewed**: YES / NO — if NO, flag as
requiring review before merge
Compounding risk warning: Bad invariant decisions
accumulate. If this branch has multiple invariant
revisions, flag the entire branch for architectural
review. Each silent invariant change multiplies the
probability of an unsalvageable codebase.
Output a structured report:
## Justification Report
**Branch**: feature/xyz
**Base**: master
**Delta**: +N/-M lines, X files changed
### Additive Bias Score: X.X (ZONE)
| Signal | Score | Detail |
|--------|-------|--------|
| Line ratio | N | +A/-D = R:1 |
| New files | N | [list] |
| Test changes | N | [list] |
| New abstractions | N | [list] |
| Workarounds | N | [list] |
### Iron Law Compliance: PASS/FAIL
[List any test logic modifications with justification]
### Change-by-Change Justification
#### file.py (+N/-M)
- **What**: [description]
- **Why**: [root cause this addresses]
- **Alternatives considered**: [what else could work]
- **Why this is minimal**: [why fewer changes won't work]
#### test_file.py (+N/-M)
- **What**: [description]
- **Justification**: [why test logic changed, if it did]
- **Iron Law status**: PASS/VIOLATION
### Risk Assessment
| Factor | Rating |
|--------|--------|
| Lines changed | LOW/MED/HIGH |
| Files touched | LOW/MED/HIGH |
| Test modifications | NONE/JUSTIFIED/VIOLATION |
| New abstractions | NONE/JUSTIFIED/UNNECESSARY |
| Overall merge risk | LOW/MED/HIGH |
### Recommendations
[List any changes that should be reconsidered,
simpler alternatives, or unnecessary additions]
When evaluating competing approaches, weight
these factors:
| Factor | Weight | Rationale |
|--------|--------|-----------|
| Fewer lines changed | HIGH | Less risk, easier review |
| No new files | HIGH | No new maintenance burden |
| No test logic changes | HIGH | Iron Law compliance |
| Root cause fix | HIGH | Prevents recurrence |
| Removes code | BONUS | Reduces maintenance surface |
| Adds abstraction | PENALTY | Only justified at 3rd use |
| Adds error handling | NEUTRAL | Only at system boundaries |
The Subtraction Test: Before accepting any change,
ask: "Could I achieve this by *removing* code instead
of *adding* it?" If yes, prefer the subtractive approach.
Justify extends proof-of-work with change-level
accountability:
Both are required before claiming work is complete.
Run proof-of-work first, then justify.
Changing test expectations to match broken code.
Fix: Revert the test change, fix the implementation.
Adding code in many files for a single-concern fix.
Fix: Find the single point of change.
Adding try/catch, null checks, or validation for
scenarios that can't happen in practice.
Fix: Trust internal code. Only validate at boundaries.
Creating a helper/utility/base class for one use case.
Fix: Inline the code. Abstract at the 3rd use.
Adding backward-compatibility code instead of
updating callers.
Fix: Update callers directly. Delete dead paths.
Changing an architectural pattern, data structure
choice, or API contract without acknowledging
that a design invariant is being revised.
Fix: Name the invariant. Present the 3 options
(preserve, layer, revise) to a human. Do not make
the judgment call yourself: models default to the
"average" of training data, and wrong invariant
decisions compound into unsalvageable codebases.
Before justifying any change, apply these questions.
If the answer to questions 4 and 5 is not concrete
evidence, the change is unjustified.
current priority?
juncture?
solution exist?
(not assumed)?
The default stance is: **this addition should not
exist.** The change must prove its necessity, not the
reviewer must prove it unnecessary.
When generating the Justification Report (Step 5), add
a Burden of Proof section:
| Change | Scrutiny Q4 Answer | Scrutiny Q5 Answer | Verdict |
|--------|--------------------|--------------------|---------|
| file.py | [evidence] | [consequence] | justified/needs_evidence/unjustified |
Changes with unjustified verdict MUST be removed or
reworked before the report passes.
When this step settles a decision with real alternatives, record it to
docs/tradeoffs.md while the reasoning is live (draft and confirm):
Skill(leyline:decision-journal) and appenda tradeoff entry (the decision, the options weighed, and what was
sacrificed; set phase to review). Show the draft; append on
confirmation.
docs/tradeoffs.md using the in-fileENTRY TEMPLATE; assign the next TR-NNN id.
> Is what you are doing a deviation of your priority?
> Is it critical to implement at this juncture?
> Rely less on AI and initial lines of thinking.
> Challenge yourself to be better, to think of a more
> elegant implementation or a simpler solution.
is reported.
modified test logic.
unjustified); no unjustified verdict survives in the final report.
human-review flag.
docs/tradeoffs.md (orthe in-file template) before the report passes.
imbue:karpathy-principles - "Surgical Changes" and"Goal-Driven Execution" principles invoke this audit
from a higher-level synthesis
leyline:additive-bias-defense - the contract thisaudit enforces in detail
imbue:proof-of-work - the validation layer thisaudit complements (proof-of-work asks "did it work?",
justify asks "did it need to exist?")
docs/quality-gates.md#skill-level-quality-gate-compositionfor the full gate-skill federation graph
Take athola/justify from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.