skills/verification/model-guided-code-repair/SKILL.md
Uses a model checker's counterexample trace to localize the fault in the model, propose a fix, and propagate that fix back to the source code. Use when a model checker (TLC, NuSMV, Spin) finds a violation and you need to turn the trace into a code change, not just understand it.
npx skillsauth add santosomar/general-secure-coding-agent-skills model-guided-code-repairInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A counterexample trace says "here's a sequence of steps that breaks your property." The repair question is: which step shouldn't have been allowed? Strengthen that step's guard, and the trace is blocked.
For TLA+-specific repair, → tlaplus-guided-code-repair. This skill is the general workflow across checkers.
┌─────────────────┐ violation ┌──────────────┐
│ Model checker │────────────────────▶│ Trace │
│ (TLC/NuSMV/Spin)│ │ analysis │
└─────────────────┘ └──────┬───────┘
▲ │
│ ┌─────▼────────┐
│ │ Pick culprit │
│ │ step │
│ └─────┬────────┘
│ check new model │
└────────────────────────────────┌─────▼────────┐
│ Strengthen │
│ guard │
└─────┬────────┘
│
┌─────▼────────┐
│ Propagate to │
│ source code │
└──────────────┘
The trace is a sequence of states s₀ → s₁ → ... → sₙ where sₙ violates the property. Walk backwards from sₙ:
sₙ, what's wrong? (Which clause of the invariant is false?)sₙ₋₁ → sₙ, what action fired? Did that action make the bad thing true?sₙ₋₁ → sₙ is a candidate culprit.sₙ₋₁), keep walking back.The culprit is the latest step that could have prevented the violation — the one where adding a guard would have blocked the trace.
Three kinds of fix, from least to most invasive:
| Fix kind | What it does | When | | -------------------- | --------------------------------------------------- | ----------------------------------------------- | | Strengthen guard | Action can't fire in the bad state | The action was legitimate, just fired too early | | Add missing action | New action to restore the invariant | Something should have happened but didn't | | Change the property | The property was wrong, not the system | The "violation" is actually intended behavior |
For a strengthened guard: The new conjunct should be the negation of whatever was true at the culprit step that shouldn't have been. Look at sₙ₋₁ — what predicate would have excluded it?
Re-run the checker. Three outcomes:
Also check: did the fix introduce deadlock? Run with deadlock checking on.
The model action maps to some code region (you built that mapping during → program-to-tlaplus-spec-generator or equivalent). The new guard conjunct translates:
| Model guard | Code change |
| ------------------------------ | ------------------------------------------------------ |
| /\ lock_holder = self | Add assert(lock_held_by_me) or wrap in lock |
| /\ Len(queue) < Max | Add if (queue.size() >= Max) return/wait |
| /\ state[p] = "ready" | Add state check: if (p.state != READY) return |
| /\ msg.epoch = current_epoch | Add epoch comparison; drop stale messages |
The code change should be at least as strong as the model guard. If the model says x > 0 and you add x > 5 in code — fine, stronger. If you add x >= 0 — weaker, the bug may still exist.
Model (NuSMV): Reader-writer lock. Property: AG !(reading & writing) — never read and write simultaneously.
Trace:
State 1: readers=0, writers=0, reading=FALSE, writing=FALSE
State 2: readers=1, writers=0, reading=TRUE, writing=FALSE [StartRead]
State 3: readers=1, writers=1, reading=TRUE, writing=TRUE [StartWrite] ← VIOLATION
Walk back: State 3 violates. Action StartWrite at 2→3 set writing=TRUE while reading=TRUE. That's the culprit.
Current StartWrite guard: writers = 0. It only checks no other writers. It doesn't check readers.
Fix: StartWrite guard becomes writers = 0 & readers = 0.
Re-check: Property holds. Deadlock check: fine (readers eventually finish → guard becomes true).
Code: Find the acquire_write_lock function. It currently checks if (writer_count == 0). Add && reader_count == 0.
FALSE makes any safety property hold and every liveness property fail. Check the action still fires on good paths.## Counterexample (key steps)
<state i> → [Action] → <state i+1> <-- culprit
## Violation
At state <n>: <which clause of the property is false>
## Culprit
Action: <name>
Fired in state: <key variables>
Why it shouldn't have: <predicate that should have blocked it>
## Model fix
<before/after of the action guard>
## Re-check result
<property status, deadlock status, new counterexamples if any>
## Code fix
File: <path>
Change: <before/after>
Mapping: model action <X> ↔ code <function/region>
development
Extracts human-readable pseudocode from a verified formal artifact (Dafny, Lean, TLA+) while preserving the verified properties as annotations, so the proof-carrying logic can be reimplemented in a production language. Use when porting verified code to an unverified target, when documenting what a formal spec actually does, or when handing a verified algorithm to an implementer.
development
Translates natural-language or pseudocode descriptions of concurrent and distributed systems into TLA+ specifications ready for the TLC model checker. Identifies state variables, actions, type invariants, safety properties, and liveness properties from the description. Use when formalizing a protocol, when the user describes a distributed algorithm to verify, when designing a consensus or locking scheme, or when starting formal verification of a concurrent system.
testing
Reduces a TLA+ model so TLC can actually check it — shrinks constants, adds state constraints, abstracts data, or applies symmetry — when the state space is too large to enumerate. Use when TLC runs out of memory, when checking takes hours, or when a spec works at N=2 and you need confidence at larger scale.
development
TLA+-specific instance of model-guided repair — reads a TLC error trace, identifies the enabling condition that should have been false, strengthens the corresponding action, and maps the fix to source code. Use when TLC reports an invariant violation or deadlock and you have the code-to-TLA+ mapping from extraction.