[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F7WCSV49KTSQDM3DCDE895":3,"skill-same:01M6F7WCSV49KTSQDM3DCDE895":82},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":45,"linkOnly":46,"markdownContent":47,"markdownBody":48,"frontMatter":49,"refName":33,"commitSha":62,"skillDirectory":63,"forksCount":64,"repositoryHtmlUrl":65,"repositoryHomepageUrl":6,"ownerHtmlUrl":66,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":67,"install":68,"resources":81},"01M6F7WCSV49KTSQDM3DCDE895","invariant-guard",null,"Correctness-first: forces writing the function contract, loop invariant, termination argument, and edge cases BEFORE code. Catches Boyer-Moore, leftmost binary search, QuickSelect traps.","boisenoise\u002Fskills-collections","boisenoise","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F193180?v=4","01M6CDBDQK6BDN55ZMA6B6PKBM","01M6CDBDVSJ3JPTBYN26FZ7TZK",12,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-invariant-guard\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000037","律师与法务","PROFESSION","适用职业",[23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],"invariant guard","guard","community","2026","invariants","loop-invariants","correctness","argument","contract","algorithms","main","contracts","writing","loop","edge","search","license","verification","code","gemini","safe","cli","2026-11-10 10:12:30",false,"---\nname: invariant-guard\ndescription: \"Correctness-first: forces writing the function contract, loop invariant, termination argument, and edge cases BEFORE code. Catches Boyer-Moore, leftmost binary search, QuickSelect traps.\"\nrisk: safe\nsource: community\nsource_repo: morsechimwai\u002Flemmaly\nsource_type: community\ndate_added: \"2026-05-26\"\nauthor: morsechimwai\ntags: [algorithms, correctness, loop-invariants, contracts, edge-cases, verification]\ntools: [claude-code, antigravity, cursor, gemini-cli, codex-cli]\nlicense: \"Apache-2.0\"\nlicense_source: \"https:\u002F\u002Fgithub.com\u002Fmorsechimwai\u002Flemmaly\u002Fblob\u002Fmain\u002FLICENSE\"\n---\n\n# invariant-guard — Correctness-First Coding\n\nThe model knows what a loop invariant is. It knows recursion needs a base case. It knows about empty lists, integer overflow, and the difference between `\u003C` and `≤`. It just does not write these down before producing code, so it ships subtle correctness bugs that tests do not catch.\n\ninvariant-guard fixes the behavior. State the invariants. State the base case. State the termination argument. State the edge cases. Then write the code — and verify that the code maintains what you stated.\n\n**Violating the letter of these rules is violating the spirit of the skill.** \"I know this algorithm\" is the exact rationalization that ships off-by-one and missing-postcondition bugs.\n\n## When to Use This Skill\n\nUse **invariant-guard** when writing or reviewing algorithms where the obvious implementation is subtly wrong:\n\n- Postcondition stronger than the loop's natural invariant: Boyer–Moore majority, Floyd's cycle detection, leftmost vs any binary search, QuickSelect partition.\n- In-place mutation with read+write pointers: dedup-in-place, partition, rotate.\n- Recursion with multiple parameters or accumulator state.\n- Off-by-one suspects with duplicates, empty inputs, boundary values.\n- Iterative refinements that must terminate: fixed-point, Newton, EM.\n- Any function where you catch yourself thinking \"I know this algorithm\" — the trap is usually in the contract, not the loop body.\n\nPairs with `lemmaly` (picks the algorithm) and `mathguard` (picks the math). Load `invariant-guard` *after* the algorithm has been chosen and *before* the loop body is written.\n\n## The Iron Law\n\n```text\nNO LOOP OR RECURSION WITHOUT A WRITTEN INVARIANT AND TERMINATION ARGUMENT\n```\n\nIf you cannot write the invariant in one sentence, you have not designed the loop. Write code anyway and you are coding by guess — and the bug will be in the case you did not enumerate.\n\n## Non-negotiable rules\n\n1. **Every loop gets a one-line invariant.** Before writing any loop, state in one sentence what is true at the top of every iteration. Examples:\n   - \"At loop top: `result` contains the sum of `a[0..i)`.\"\n   - \"At loop top: `lo ≤ target_position ≤ hi`.\"\n   - \"At loop top: `seen` contains every element processed so far; `dups` contains every element that appeared at least twice.\"\n\n   If you cannot write the invariant in one sentence, you have not designed the loop yet.\n\n2. **Every loop gets a one-line termination argument.** Name the quantity that strictly decreases (or strictly increases toward a bound) on every iteration. Examples:\n   - \"`hi − lo` strictly decreases each iteration.\"\n   - \"`i` increases by 1 and is bounded above by `n`.\"\n   - \"`stack.length` strictly decreases each pop; nothing pushes inside this branch.\"\n\n   No termination argument, no loop.\n\n3. **Every recursion gets an explicit base case and a measure.** Before writing a recursive function, state:\n   - The base case(s) — the smallest inputs that return without recursing.\n   - The measure — a non-negative integer that strictly decreases on every recursive call (e.g. `len(xs)`, `hi − lo`, `depth`, `n`).\n   - The combination — how the recursive results combine into the answer.\n\n   No base case + measure, no recursion. (Mutual recursion: state the measure across the cycle.)\n\n4. **List edge cases before writing, not after.** For every function operating on a collection or number, list which of these apply and how they behave:\n   - Empty input (`[]`, `\"\"`, `null`, `undefined`, `None`).\n   - Singleton (`[x]`).\n   - All-equal elements.\n   - Already-sorted \u002F reverse-sorted input.\n   - Duplicates (when uniqueness is assumed).\n   - Negative numbers, zero, exactly the boundary value.\n   - Integer overflow \u002F underflow at the type max\u002Fmin.\n   - NaN, ±Infinity, `-0`, denormals (for floats).\n   - Off-by-one boundaries: index 0, index n−1, index n, length 0, length 1.\n   - Concurrent modification while iterating.\n\n   The cases that apply must each have a one-phrase expected behavior written down.\n\n5. **Make illegal states unreachable, not just unhandled.** Prefer encoding constraints in types and structure so the wrong state cannot be constructed:\n   - Sum type over boolean flag soup (`Loading | Loaded(data) | Error(msg)` not `{loading, data, error}`).\n   - Newtype for IDs that must not be swapped (`UserId` vs `OrderId`).\n   - Non-empty list type when the function requires at least one element.\n   - Parsed value at the boundary, not validated repeatedly downstream (parse-don't-validate).\n\n   If the language cannot encode it, write the invariant as a comment and assert it at the boundary.\n\n## The pre-write protocol\n\nBefore producing non-trivial code that has loops, recursion, or non-trivial state, your message must contain — in this order:\n\n1. **Function contract** — preconditions, postconditions, and what the function returns. One line each.\n2. **Loop invariants** — one per loop. (Rule 1.)\n3. **Termination arguments** — one per loop or recursion. (Rules 2, 3.)\n4. **Base cases and measure** — for recursion. (Rule 3.)\n5. **Edge case table** — bullets, one per applicable case, with expected behavior. (Rule 4.)\n6. **Illegal states made unrepresentable** — name the types or asserts that enforce invariants. (Rule 5.)\n7. **The code.**\n8. **Self-check** — one line per loop confirming the invariant holds at top, body preserves it, and exit implies postcondition.\n\nIf any of 1–6 is missing, do not emit code.\n\n## Worked trap — Boyer–Moore majority vote\n\nThis is the canonical \"the trap is in the contract, not the loop body\" case.\n\n**Naive baseline (what gets shipped without the skill):**\n\n```typescript\nfunction findMajority(arr: number[]): number | null {\n  if (arr.length === 0) return null;\n  let candidate = arr[0], count = 0;\n  for (const x of arr) {\n    if (count === 0) candidate = x;\n    if (x === candidate) count++; else count--;\n  }\n  return candidate;   \u002F\u002F BUG: returns the candidate even when no majority exists\n}\n```\n\nThis implementation fails on `[1,2,3]` (returns `3`, expected `null`) and `[2,2,1,1]` (returns `2`, expected `null`). The voting loop is correct; the postcondition is wrong.\n\n**Why the protocol catches it.** Writing **step 1 (function contract)** forces the postcondition in plain language:\n\n> Returns `x` iff `count(x, arr) > arr.length \u002F 2`; else `null`.\n\nThen writing **step 2 (loop invariant)** forces the invariant of the voting pass:\n\n> The processed prefix can be partitioned into pairs of unequal values plus `count` copies of `candidate`. This cancellation invariant implies the final candidate is the majority if a strict majority exists.\n\nThese two statements are not equivalent. The loop invariant guarantees \"if a majority exists, it is the candidate\" — not \"the candidate is a majority.\" Once you write both down, the gap is visible: you need a second pass to verify, or the postcondition is unmet.\n\n**Correct implementation that survives the protocol:**\n\n```typescript\nfunction findMajority(arr: number[]): number | null {\n  if (arr.length === 0) return null;\n  \u002F\u002F Pass 1: vote.\n  let candidate = arr[0], count = 0;\n  \u002F\u002F inv: processed prefix = unequal canceled pairs plus count copies of candidate.\n  \u002F\u002F term: the finite array iterator advances once per iteration.\n  for (const x of arr) {\n    if (count === 0) candidate = x;\n    if (x === candidate) count++; else count--;\n  }\n  \u002F\u002F Pass 2: verify — the voting invariant is strictly weaker than the postcondition.\n  let tally = 0;\n  \u002F\u002F inv: tally = count of candidate in arr[0..i).\n  for (const x of arr) if (x === candidate) tally++;\n  return tally * 2 > arr.length ? candidate : null;\n}\n```\n\n**Pattern to generalize.** The same trap appears in:\n\n- **Floyd's cycle detection** — finding the meeting point tells you a cycle exists, *not* where it starts. You need a second walk.\n- **Two-pointer \"find any\"** vs **\"find leftmost\"** — the loop invariant for one does not satisfy the postcondition of the other.\n- **QuickSelect partition** — the loop returns a position; the postcondition is that the element at that position is the k-th smallest. Off by one in the partition invariant silently breaks it.\n- **DP with reconstruction** — the table tells you the optimum value; reconstructing the optimum path needs separate invariants on the choice array.\n\nIn every case: **write the postcondition first; write the loop invariant second; check that the second implies the first. If not, you are missing a pass, a check, or an auxiliary state.**\n\n## Canonical example — binary search for the leftmost match\n\nMost \"I know binary search\" implementations are written for \"find any match.\" The trap is the postcondition.\n\n**Problem.** Given a sorted array with duplicates, return the index of the **leftmost** occurrence of `target`, or `-1`.\n\n### Without the protocol — returns any match\n\n```ts\nfunction leftmost(a: number[], target: number): number {\n  let lo = 0, hi = a.length - 1;\n  while (lo \u003C= hi) {\n    const mid = lo + Math.floor((hi - lo) \u002F 2);\n    if (a[mid] === target) return mid;       \u002F\u002F returns ANY occurrence\n    if (a[mid] \u003C target) lo = mid + 1; else hi = mid - 1;\n  }\n  return -1;\n}\n\u002F\u002F leftmost([1,2,2,2,3], 2) → may return 2, not 1\n```\n\nThe loop invariant (\"target lies in `a[lo..hi]` if anywhere\") is satisfied. But the postcondition (\"returned index is the *smallest* `i` with `a[i] === target`\") is strictly stronger. The loop body's early return abandons the search before reaching the leftmost.\n\n### With the protocol — contract-driven leftmost\n\n```ts\nfunction leftmost(a: number[], target: number): number {\n  \u002F\u002F contract:\n  \u002F\u002F   pre:  a contains finite numbers sorted ascending; target is finite\n  \u002F\u002F   post: returns smallest i with a[i] === target, or -1 if absent\n  let lo = 0, hi = a.length;                 \u002F\u002F half-open [lo, hi)\n  \u002F\u002F inv: 0 \u003C= lo \u003C= hi \u003C= n; indices \u003C lo are \u003C target; indices >= hi are >= target\n  \u002F\u002F term: nonnegative integer hi - lo strictly decreases each iteration\n  while (lo \u003C hi) {\n    const mid = lo + Math.floor((hi - lo) \u002F 2);\n    if (a[mid] \u003C target) lo = mid + 1; else hi = mid;\n  }\n  \u002F\u002F exit: lo === hi, and by invariant lo is the leftmost index where a[lo] >= target\n  return lo \u003C a.length && a[lo] === target ? lo : -1;\n}\n```\n\nSame loop shape. The difference is the contract was written first — and the loop body was chosen to maintain an invariant that *implies* the postcondition.\n\n## Common invariant patterns to reach for\n\n| Loop \u002F algorithm shape | Canonical invariant | Termination |\n|---|---|---|\n| Linear scan accumulating | `acc = f(a[0..i))` at top | `i` increases by 1, bounded by `n` |\n| Two-pointer (sorted) | `target (if any) lies in a[lo..hi]` | `hi − lo` strictly decreases |\n| Binary search | `target (if present) ∈ a[lo..hi]` and `a[lo..hi]` non-empty | `hi − lo` strictly halves |\n| Sliding window | window `[l..r)` satisfies the constraint; answer ≥ best so far | `r` advances at least once per outer iter |\n| BFS | every node at distance \u003C d has been popped; queue contains some at distance d | finite graph; each node enqueued at most once, processed count increases |\n| DFS \u002F recursion on tree | result for subtree rooted at v = combine(children results) | depth (or remaining nodes) strictly decreases |\n| Divide and conquer | result on `a[lo..hi]` = combine(results on the two halves) | `hi − lo` strictly halves |\n| Greedy with priority queue | extracted item is globally optimal for the remaining problem | prove a bounded total number of insertions\u002Fextractions; heap size may grow |\n| Union-Find op | `find(x)` always returns the canonical root of x's component | tree height bounded by O(log n) (with rank) |\n| In-place partition | `a[0..i)` \u003C pivot; `a[i..j)` ≥ pivot; `a[j..n)` unseen | `n − j` strictly decreases |\n\n## Edge case table — defaults to consider\n\n| Input shape | Cases to check |\n|---|---|\n| Array \u002F list | empty, singleton, all-equal, sorted, reversed, with duplicates |\n| String | empty, single char, all whitespace, unicode (surrogates, combining), bytes vs code points |\n| Integer | 0, 1, −1, MIN, MAX, MAX − 1, near overflow in arithmetic, division by 0 |\n| Float | 0.0, −0.0, NaN, ±Inf, denormal, choose exact or tolerance semantics from the contract; no universal epsilon |\n| Map \u002F dict | empty, missing key (default vs error), key collision semantics |\n| Tree \u002F graph | empty, single node, cycle (if undirected), self-loop, multigraph, disconnected |\n| Stream \u002F iterator | empty, infinite, single yield, exception mid-iteration |\n| Time \u002F date | DST transition, leap second\u002Fday, timezone offset, epoch boundary |\n| Concurrent | empty contention, single thread, max contention, cancellation mid-op |\n\n## Output discipline\n\nCode you emit must:\n\n- Have one comment per loop stating the invariant (use `\u002F\u002F inv:` or `# inv:`).\n- Have one comment per recursion stating the base case and measure.\n- Handle every edge case you listed in step 5, or explicitly delegate (\"throws on empty — caller responsibility\").\n- Assert preconditions at function entry when the language supports it cheaply.\n- Use types (sum types, newtypes, non-empty, non-null) over runtime checks where the language allows.\n\n## When to escalate or redirect\n\n- The function is performance-critical and you have not picked the algorithm — go back to **`lemmaly`** first; pick the algorithm, then state its invariants here.\n- The technique is mathematical (probabilistic, FFT, geometry) — load **`mathguard`**; invariants for approximate algorithms include ε-bounds, not equality.\n- The code is concurrent — invariants must account for interleaving; explicitly state \"single-threaded only\" if that is the assumption.\n\n## Rationalizations to watch for\n\n| Excuse | Reality |\n| --- | --- |\n| \"I know this algorithm — single pass, done.\" | Knowing the loop ≠ knowing the contract. The trap usually lives in the postcondition the loop does not enforce. |\n| \"I traced it in my head, it works.\" | Mental tracing skips edge cases. Write the invariant; check it implies the postcondition. |\n| \"Edge cases are obvious.\" | Then write them down in 30 seconds. If they are obvious, the table is cheap. If they are not, the table just saved you. |\n| \"Tests will catch it.\" | Tests catch the examples you thought of. The trap is the example you did not. A correctly proved postcondition covers its assumptions; writing it alone proves nothing. |\n| \"The postcondition is implied.\" | An invariant need not equal the postcondition; it must imply it together with the exit condition and preconditions. When they differ (Boyer–Moore, leftmost search, QuickSelect), you need a second pass, an extra check, or auxiliary state. |\n| \"Adding a verification pass feels redundant.\" | Boyer–Moore voting + verification is still O(n). \"Feels redundant\" is the rationalization that ships the bug. |\n\n## Red flags — STOP and write the invariant first\n\n- About to write `while (...)` without having stated what is true on entry.\n- About to write `if (i === n − 1)` or `if (i === n)` — boundary suspicious, restate the invariant.\n- About to recurse without naming the base case in this message.\n- About to write `\u002F\u002F TODO: handle empty` — handle it now or change the type so empty is impossible.\n- About to use `==` on floats.\n- About to compare across signed\u002Funsigned or across types where overflow rolls.\n- About to silently swallow an error in the middle of a loop (\"just continue\").\n- Tests pass but you did not actually state what the function guarantees.\n- \"It works on the examples I tried.\"\n\n## Verification checklist\n\nBefore claiming the function is correct:\n\n- [ ] Every loop has a one-line `\u002F\u002F inv:` comment in code.\n- [ ] Every loop has a termination argument written down (in comment or PR description).\n- [ ] Every recursion names its base case and measure in code.\n- [ ] The function's postcondition is written and is implied by the exit state of the last loop.\n- [ ] Every applicable edge case from the table has a test or an explicit \"delegated to caller\" note.\n- [ ] At least one test exercises each non-trivial boundary (empty, singleton, max, off-by-one).\n- [ ] Illegal states the function rejects are either unrepresentable in the type, or asserted at entry.\n- [ ] For approximate\u002Frandomized algorithms (escalated to mathguard): ε-bounds are part of the postcondition, not equality.\n\nCannot check every box? The code is example-correct, not behavior-correct. Either fill the gap or downgrade the function's claimed contract.\n\n## Limitations\n\n- **Not an automated prover.** invariant-guard requires the author to *write* invariants; it does not mechanically check them. Pair with property-based tests for stronger evidence.\n- **Concurrency is out of scope by default.** Stated invariants assume single-threaded execution unless explicitly extended; multi-threaded reasoning needs additional happens-before \u002F linearizability arguments.\n- **Float and overflow edge cases are language-specific.** The edge-case table is a checklist, not a substitute for understanding your language's numeric semantics.\n- **Will slow down trivial code.** For one-liners that obviously cannot fail, the protocol is overhead; reserve it for non-trivial loops, recursion, and in-place mutation.\n- **Documentation is the only enforcement.** If the author skips writing the invariants, this skill cannot detect that — pair with code review or a PR template that asks for the contract.\n\n## The thesis, in one line\n\n> **Tests verify examples. Invariants verify behavior. AI assistants ship example-correct, behavior-wrong code by default. invariant-guard makes them reason about behavior first.**\n\n## Related Skills\n\n- `lemmaly` — algorithm choice must be settled before invariants; load lemmaly first if the algorithm family is unclear.\n- `mathguard` — ε-bounded postconditions for approximate \u002F randomized algorithms.\n- `complexity-cuts` — if 3+ optimization transformations have failed tests, the bug is a missing contract, not a missing optimization — escalate here.\n","# invariant-guard — Correctness-First Coding\n\nThe model knows what a loop invariant is. It knows recursion needs a base case. It knows about empty lists, integer overflow, and the difference between `\u003C` and `≤`. It just does not write these down before producing code, so it ships subtle correctness bugs that tests do not catch.\n\ninvariant-guard fixes the behavior. State the invariants. State the base case. State the termination argument. State the edge cases. Then write the code — and verify that the code maintains what you stated.\n\n**Violating the letter of these rules is violating the spirit of the skill.** \"I know this algorithm\" is the exact rationalization that ships off-by-one and missing-postcondition bugs.\n\n## When to Use This Skill\n\nUse **invariant-guard** when writing or reviewing algorithms where the obvious implementation is subtly wrong:\n\n- Postcondition stronger than the loop's natural invariant: Boyer–Moore majority, Floyd's cycle detection, leftmost vs any binary search, QuickSelect partition.\n- In-place mutation with read+write pointers: dedup-in-place, partition, rotate.\n- Recursion with multiple parameters or accumulator state.\n- Off-by-one suspects with duplicates, empty inputs, boundary values.\n- Iterative refinements that must terminate: fixed-point, Newton, EM.\n- Any function where you catch yourself thinking \"I know this algorithm\" — the trap is usually in the contract, not the loop body.\n\nPairs with `lemmaly` (picks the algorithm) and `mathguard` (picks the math). Load `invariant-guard` *after* the algorithm has been chosen and *before* the loop body is written.\n\n## The Iron Law\n\n```text\nNO LOOP OR RECURSION WITHOUT A WRITTEN INVARIANT AND TERMINATION ARGUMENT\n```\n\nIf you cannot write the invariant in one sentence, you have not designed the loop. Write code anyway and you are coding by guess — and the bug will be in the case you did not enumerate.\n\n## Non-negotiable rules\n\n1. **Every loop gets a one-line invariant.** Before writing any loop, state in one sentence what is true at the top of every iteration. Examples:\n   - \"At loop top: `result` contains the sum of `a[0..i)`.\"\n   - \"At loop top: `lo ≤ target_position ≤ hi`.\"\n   - \"At loop top: `seen` contains every element processed so far; `dups` contains every element that appeared at least twice.\"\n\n   If you cannot write the invariant in one sentence, you have not designed the loop yet.\n\n2. **Every loop gets a one-line termination argument.** Name the quantity that strictly decreases (or strictly increases toward a bound) on every iteration. Examples:\n   - \"`hi − lo` strictly decreases each iteration.\"\n   - \"`i` increases by 1 and is bounded above by `n`.\"\n   - \"`stack.length` strictly decreases each pop; nothing pushes inside this branch.\"\n\n   No termination argument, no loop.\n\n3. **Every recursion gets an explicit base case and a measure.** Before writing a recursive function, state:\n   - The base case(s) — the smallest inputs that return without recursing.\n   - The measure — a non-negative integer that strictly decreases on every recursive call (e.g. `len(xs)`, `hi − lo`, `depth`, `n`).\n   - The combination — how the recursive results combine into the answer.\n\n   No base case + measure, no recursion. (Mutual recursion: state the measure across the cycle.)\n\n4. **List edge cases before writing, not after.** For every function operating on a collection or number, list which of these apply and how they behave:\n   - Empty input (`[]`, `\"\"`, `null`, `undefined`, `None`).\n   - Singleton (`[x]`).\n   - All-equal elements.\n   - Already-sorted \u002F reverse-sorted input.\n   - Duplicates (when uniqueness is assumed).\n   - Negative numbers, zero, exactly the boundary value.\n   - Integer overflow \u002F underflow at the type max\u002Fmin.\n   - NaN, ±Infinity, `-0`, denormals (for floats).\n   - Off-by-one boundaries: index 0, index n−1, index n, length 0, length 1.\n   - Concurrent modification while iterating.\n\n   The cases that apply must each have a one-phrase expected behavior written down.\n\n5. **Make illegal states unreachable, not just unhandled.** Prefer encoding constraints in types and structure so the wrong state cannot be constructed:\n   - Sum type over boolean flag soup (`Loading | Loaded(data) | Error(msg)` not `{loading, data, error}`).\n   - Newtype for IDs that must not be swapped (`UserId` vs `OrderId`).\n   - Non-empty list type when the function requires at least one element.\n   - Parsed value at the boundary, not validated repeatedly downstream (parse-don't-validate).\n\n   If the language cannot encode it, write the invariant as a comment and assert it at the boundary.\n\n## The pre-write protocol\n\nBefore producing non-trivial code that has loops, recursion, or non-trivial state, your message must contain — in this order:\n\n1. **Function contract** — preconditions, postconditions, and what the function returns. One line each.\n2. **Loop invariants** — one per loop. (Rule 1.)\n3. **Termination arguments** — one per loop or recursion. (Rules 2, 3.)\n4. **Base cases and measure** — for recursion. (Rule 3.)\n5. **Edge case table** — bullets, one per applicable case, with expected behavior. (Rule 4.)\n6. **Illegal states made unrepresentable** — name the types or asserts that enforce invariants. (Rule 5.)\n7. **The code.**\n8. **Self-check** — one line per loop confirming the invariant holds at top, body preserves it, and exit implies postcondition.\n\nIf any of 1–6 is missing, do not emit code.\n\n## Worked trap — Boyer–Moore majority vote\n\nThis is the canonical \"the trap is in the contract, not the loop body\" case.\n\n**Naive baseline (what gets shipped without the skill):**\n\n```typescript\nfunction findMajority(arr: number[]): number | null {\n  if (arr.length === 0) return null;\n  let candidate = arr[0], count = 0;\n  for (const x of arr) {\n    if (count === 0) candidate = x;\n    if (x === candidate) count++; else count--;\n  }\n  return candidate;   \u002F\u002F BUG: returns the candidate even when no majority exists\n}\n```\n\nThis implementation fails on `[1,2,3]` (returns `3`, expected `null`) and `[2,2,1,1]` (returns `2`, expected `null`). The voting loop is correct; the postcondition is wrong.\n\n**Why the protocol catches it.** Writing **step 1 (function contract)** forces the postcondition in plain language:\n\n> Returns `x` iff `count(x, arr) > arr.length \u002F 2`; else `null`.\n\nThen writing **step 2 (loop invariant)** forces the invariant of the voting pass:\n\n> The processed prefix can be partitioned into pairs of unequal values plus `count` copies of `candidate`. This cancellation invariant implies the final candidate is the majority if a strict majority exists.\n\nThese two statements are not equivalent. The loop invariant guarantees \"if a majority exists, it is the candidate\" — not \"the candidate is a majority.\" Once you write both down, the gap is visible: you need a second pass to verify, or the postcondition is unmet.\n\n**Correct implementation that survives the protocol:**\n\n```typescript\nfunction findMajority(arr: number[]): number | null {\n  if (arr.length === 0) return null;\n  \u002F\u002F Pass 1: vote.\n  let candidate = arr[0], count = 0;\n  \u002F\u002F inv: processed prefix = unequal canceled pairs plus count copies of candidate.\n  \u002F\u002F term: the finite array iterator advances once per iteration.\n  for (const x of arr) {\n    if (count === 0) candidate = x;\n    if (x === candidate) count++; else count--;\n  }\n  \u002F\u002F Pass 2: verify — the voting invariant is strictly weaker than the postcondition.\n  let tally = 0;\n  \u002F\u002F inv: tally = count of candidate in arr[0..i).\n  for (const x of arr) if (x === candidate) tally++;\n  return tally * 2 > arr.length ? candidate : null;\n}\n```\n\n**Pattern to generalize.** The same trap appears in:\n\n- **Floyd's cycle detection** — finding the meeting point tells you a cycle exists, *not* where it starts. You need a second walk.\n- **Two-pointer \"find any\"** vs **\"find leftmost\"** — the loop invariant for one does not satisfy the postcondition of the other.\n- **QuickSelect partition** — the loop returns a position; the postcondition is that the element at that position is the k-th smallest. Off by one in the partition invariant silently breaks it.\n- **DP with reconstruction** — the table tells you the optimum value; reconstructing the optimum path needs separate invariants on the choice array.\n\nIn every case: **write the postcondition first; write the loop invariant second; check that the second implies the first. If not, you are missing a pass, a check, or an auxiliary state.**\n\n## Canonical example — binary search for the leftmost match\n\nMost \"I know binary search\" implementations are written for \"find any match.\" The trap is the postcondition.\n\n**Problem.** Given a sorted array with duplicates, return the index of the **leftmost** occurrence of `target`, or `-1`.\n\n### Without the protocol — returns any match\n\n```ts\nfunction leftmost(a: number[], target: number): number {\n  let lo = 0, hi = a.length - 1;\n  while (lo \u003C= hi) {\n    const mid = lo + Math.floor((hi - lo) \u002F 2);\n    if (a[mid] === target) return mid;       \u002F\u002F returns ANY occurrence\n    if (a[mid] \u003C target) lo = mid + 1; else hi = mid - 1;\n  }\n  return -1;\n}\n\u002F\u002F leftmost([1,2,2,2,3], 2) → may return 2, not 1\n```\n\nThe loop invariant (\"target lies in `a[lo..hi]` if anywhere\") is satisfied. But the postcondition (\"returned index is the *smallest* `i` with `a[i] === target`\") is strictly stronger. The loop body's early return abandons the search before reaching the leftmost.\n\n### With the protocol — contract-driven leftmost\n\n```ts\nfunction leftmost(a: number[], target: number): number {\n  \u002F\u002F contract:\n  \u002F\u002F   pre:  a contains finite numbers sorted ascending; target is finite\n  \u002F\u002F   post: returns smallest i with a[i] === target, or -1 if absent\n  let lo = 0, hi = a.length;                 \u002F\u002F half-open [lo, hi)\n  \u002F\u002F inv: 0 \u003C= lo \u003C= hi \u003C= n; indices \u003C lo are \u003C target; indices >= hi are >= target\n  \u002F\u002F term: nonnegative integer hi - lo strictly decreases each iteration\n  while (lo \u003C hi) {\n    const mid = lo + Math.floor((hi - lo) \u002F 2);\n    if (a[mid] \u003C target) lo = mid + 1; else hi = mid;\n  }\n  \u002F\u002F exit: lo === hi, and by invariant lo is the leftmost index where a[lo] >= target\n  return lo \u003C a.length && a[lo] === target ? lo : -1;\n}\n```\n\nSame loop shape. The difference is the contract was written first — and the loop body was chosen to maintain an invariant that *implies* the postcondition.\n\n## Common invariant patterns to reach for\n\n| Loop \u002F algorithm shape | Canonical invariant | Termination |\n|---|---|---|\n| Linear scan accumulating | `acc = f(a[0..i))` at top | `i` increases by 1, bounded by `n` |\n| Two-pointer (sorted) | `target (if any) lies in a[lo..hi]` | `hi − lo` strictly decreases |\n| Binary search | `target (if present) ∈ a[lo..hi]` and `a[lo..hi]` non-empty | `hi − lo` strictly halves |\n| Sliding window | window `[l..r)` satisfies the constraint; answer ≥ best so far | `r` advances at least once per outer iter |\n| BFS | every node at distance \u003C d has been popped; queue contains some at distance d | finite graph; each node enqueued at most once, processed count increases |\n| DFS \u002F recursion on tree | result for subtree rooted at v = combine(children results) | depth (or remaining nodes) strictly decreases |\n| Divide and conquer | result on `a[lo..hi]` = combine(results on the two halves) | `hi − lo` strictly halves |\n| Greedy with priority queue | extracted item is globally optimal for the remaining problem | prove a bounded total number of insertions\u002Fextractions; heap size may grow |\n| Union-Find op | `find(x)` always returns the canonical root of x's component | tree height bounded by O(log n) (with rank) |\n| In-place partition | `a[0..i)` \u003C pivot; `a[i..j)` ≥ pivot; `a[j..n)` unseen | `n − j` strictly decreases |\n\n## Edge case table — defaults to consider\n\n| Input shape | Cases to check |\n|---|---|\n| Array \u002F list | empty, singleton, all-equal, sorted, reversed, with duplicates |\n| String | empty, single char, all whitespace, unicode (surrogates, combining), bytes vs code points |\n| Integer | 0, 1, −1, MIN, MAX, MAX − 1, near overflow in arithmetic, division by 0 |\n| Float | 0.0, −0.0, NaN, ±Inf, denormal, choose exact or tolerance semantics from the contract; no universal epsilon |\n| Map \u002F dict | empty, missing key (default vs error), key collision semantics |\n| Tree \u002F graph | empty, single node, cycle (if undirected), self-loop, multigraph, disconnected |\n| Stream \u002F iterator | empty, infinite, single yield, exception mid-iteration |\n| Time \u002F date | DST transition, leap second\u002Fday, timezone offset, epoch boundary |\n| Concurrent | empty contention, single thread, max contention, cancellation mid-op |\n\n## Output discipline\n\nCode you emit must:\n\n- Have one comment per loop stating the invariant (use `\u002F\u002F inv:` or `# inv:`).\n- Have one comment per recursion stating the base case and measure.\n- Handle every edge case you listed in step 5, or explicitly delegate (\"throws on empty — caller responsibility\").\n- Assert preconditions at function entry when the language supports it cheaply.\n- Use types (sum types, newtypes, non-empty, non-null) over runtime checks where the language allows.\n\n## When to escalate or redirect\n\n- The function is performance-critical and you have not picked the algorithm — go back to **`lemmaly`** first; pick the algorithm, then state its invariants here.\n- The technique is mathematical (probabilistic, FFT, geometry) — load **`mathguard`**; invariants for approximate algorithms include ε-bounds, not equality.\n- The code is concurrent — invariants must account for interleaving; explicitly state \"single-threaded only\" if that is the assumption.\n\n## Rationalizations to watch for\n\n| Excuse | Reality |\n| --- | --- |\n| \"I know this algorithm — single pass, done.\" | Knowing the loop ≠ knowing the contract. The trap usually lives in the postcondition the loop does not enforce. |\n| \"I traced it in my head, it works.\" | Mental tracing skips edge cases. Write the invariant; check it implies the postcondition. |\n| \"Edge cases are obvious.\" | Then write them down in 30 seconds. If they are obvious, the table is cheap. If they are not, the table just saved you. |\n| \"Tests will catch it.\" | Tests catch the examples you thought of. The trap is the example you did not. A correctly proved postcondition covers its assumptions; writing it alone proves nothing. |\n| \"The postcondition is implied.\" | An invariant need not equal the postcondition; it must imply it together with the exit condition and preconditions. When they differ (Boyer–Moore, leftmost search, QuickSelect), you need a second pass, an extra check, or auxiliary state. |\n| \"Adding a verification pass feels redundant.\" | Boyer–Moore voting + verification is still O(n). \"Feels redundant\" is the rationalization that ships the bug. |\n\n## Red flags — STOP and write the invariant first\n\n- About to write `while (...)` without having stated what is true on entry.\n- About to write `if (i === n − 1)` or `if (i === n)` — boundary suspicious, restate the invariant.\n- About to recurse without naming the base case in this message.\n- About to write `\u002F\u002F TODO: handle empty` — handle it now or change the type so empty is impossible.\n- About to use `==` on floats.\n- About to compare across signed\u002Funsigned or across types where overflow rolls.\n- About to silently swallow an error in the middle of a loop (\"just continue\").\n- Tests pass but you did not actually state what the function guarantees.\n- \"It works on the examples I tried.\"\n\n## Verification checklist\n\nBefore claiming the function is correct:\n\n- [ ] Every loop has a one-line `\u002F\u002F inv:` comment in code.\n- [ ] Every loop has a termination argument written down (in comment or PR description).\n- [ ] Every recursion names its base case and measure in code.\n- [ ] The function's postcondition is written and is implied by the exit state of the last loop.\n- [ ] Every applicable edge case from the table has a test or an explicit \"delegated to caller\" note.\n- [ ] At least one test exercises each non-trivial boundary (empty, singleton, max, off-by-one).\n- [ ] Illegal states the function rejects are either unrepresentable in the type, or asserted at entry.\n- [ ] For approximate\u002Frandomized algorithms (escalated to mathguard): ε-bounds are part of the postcondition, not equality.\n\nCannot check every box? The code is example-correct, not behavior-correct. Either fill the gap or downgrade the function's claimed contract.\n\n## Limitations\n\n- **Not an automated prover.** invariant-guard requires the author to *write* invariants; it does not mechanically check them. Pair with property-based tests for stronger evidence.\n- **Concurrency is out of scope by default.** Stated invariants assume single-threaded execution unless explicitly extended; multi-threaded reasoning needs additional happens-before \u002F linearizability arguments.\n- **Float and overflow edge cases are language-specific.** The edge-case table is a checklist, not a substitute for understanding your language's numeric semantics.\n- **Will slow down trivial code.** For one-liners that obviously cannot fail, the protocol is overhead; reserve it for non-trivial loops, recursion, and in-place mutation.\n- **Documentation is the only enforcement.** If the author skips writing the invariants, this skill cannot detect that — pair with code review or a PR template that asks for the contract.\n\n## The thesis, in one line\n\n> **Tests verify examples. Invariants verify behavior. AI assistants ship example-correct, behavior-wrong code by default. invariant-guard makes them reason about behavior first.**\n\n## Related Skills\n\n- `lemmaly` — algorithm choice must be settled before invariants; load lemmaly first if the algorithm family is unclear.\n- `mathguard` — ε-bounded postconditions for approximate \u002F randomized algorithms.\n- `complexity-cuts` — if 3+ optimization transformations have failed tests, the bug is a missing contract, not a missing optimization — escalate here.\n",{"name":5,"risk":43,"tags":50,"tools":52,"author":58,"source":25,"license":14,"date_added":59,"description":7,"source_repo":60,"source_type":25,"license_source":61},[32,29,28,34,51,40],"edge-cases",[53,54,55,56,57],"claude-code","antigravity","cursor","gemini-cli","codex-cli","morsechimwai","2026-05-26","morsechimwai\u002Flemmaly","https:\u002F\u002Fgithub.com\u002Fmorsechimwai\u002Flemmaly\u002Fblob\u002Fmain\u002FLICENSE","7bf2279c8a65758319526a721d5c4b75683aec9c","skills\u002Fantigravity-invariant-guard",3,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections","https:\u002F\u002Fgithub.com\u002Fboisenoise","2026-11-11 14:20:43",{"source":8,"skillName":5,"commands":69,"prompt":79,"zipAvailable":80},[70,73,76],{"tool":71,"command":72},"npx","npx skills add boisenoise\u002Fskills-collections --skill invariant-guard",{"tool":74,"command":75},"bunx","bunx skills add boisenoise\u002Fskills-collections --skill invariant-guard",{"tool":77,"command":78},"pnpm","pnpm dlx skills add boisenoise\u002Fskills-collections --skill invariant-guard","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-invariant-guard\u002FSKILL.md",true,[],[83,110,136,160,179,202],{"skillNo":84,"name":85,"nameZh":6,"description":86,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":87,"sourceHtmlUrl":88,"categories":89,"keywords":90,"publishedAt":45},"01M6WNH92BHVFXHGPDZCPJ0AG0","web-access","Use Treg for public web or platform data only when existing search, browser and connected tools cannot cover the task, with explicit cost and data boundaries.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-web-access\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-web-access\u002FSKILL.md",[],[91,92,93,94,95,96,38,97,98,99,100,101,102,103,104,105,106,107,108,109,26],"web","access","web access","official","development","browser","data","Tools","read","fetch","only","youtube","free tools","platform","critical","free","cover","normal","when",{"skillNo":111,"name":112,"nameZh":6,"description":113,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":114,"sourceHtmlUrl":115,"categories":116,"keywords":122,"publishedAt":45},"01M6WNF9XAYAB2CZDDJBAKNM93","treg","Use the Treg CLI or MCP catalog to discover, price and call authorized external tools with team-scoped credentials and a user-approved budget.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-treg\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-treg\u002FSKILL.md",[117],{"categoryNo":118,"categoryName":119,"taxonomyType":120,"taxonomyTypeDesc":121},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[112,123,44,124,94,95,39,98,125,126,127,128,129,130,131,105,132,133,134,135,26],"MCP","github","Budget","credentials","team","catalog","use","call","discover","external tools","with","scoped","blob",{"skillNo":137,"name":138,"nameZh":6,"description":139,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":140,"sourceHtmlUrl":141,"categories":142,"keywords":146,"publishedAt":45},"01M6WN93MEAZ4E41DYAHJRPHQT","recruit","Turn a job description into an evidence-linked candidate shortlist through Treg, with job-related ranking and optional authorized business contact enrichment.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-recruit\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-recruit\u002FSKILL.md",[143],{"categoryNo":144,"categoryName":145,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000038","人力资源",[138,124,94,39,147,148,149,150,151,152,105,153,154,155,133,156,157,158,159,135,112,26],"https","email","enrichment","business","evidence","phone","ranking","post","job description","for","person","candidate","reason",{"skillNo":161,"name":162,"nameZh":6,"description":163,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":164,"sourceHtmlUrl":165,"categories":166,"keywords":167,"publishedAt":45},"01M6WN8R5RXSMHM9B4D9FAFX42","rea-growth","Analyze a company’s public growth and content through Treg, distinguish observations from hypotheses, and produce an evidence-linked playbook within an approved budget.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-rea-growth\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-rea-growth\u002FSKILL.md",[],[168,169,170,124,94,39,171,172,147,173,125,174,175,176,151,105,177,178,135,112,26],"growth","rea","rea growth","produce","content","playbook","marketing","analyze","public","observations","company",{"skillNo":180,"name":181,"nameZh":6,"description":182,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":183,"sourceHtmlUrl":184,"categories":185,"keywords":186,"publishedAt":45},"01M6WN2EVE3RQRGWAKBT4S7YT2","make-ugc","Create original AI UGC-style product videos through Treg: research hooks, approve a licensed presenter and voice, generate clips, add captions and report costs.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-make-ugc\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-make-ugc\u002FSKILL.md",[],[187,188,124,189,94,190,39,191,192,193,194,195,105,196,197,198,199,200,201,135],"make","make ugc","research","create","Video","Captions","ads","short","hooks","head","style","niche","trending","talking-head","approve",{"skillNo":203,"name":204,"nameZh":6,"description":205,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":206,"sourceHtmlUrl":207,"categories":208,"keywords":209,"publishedAt":45},"01M6WN10ARAA63Y8K1YNGXJ2RM","lead-signals","Find and qualify public buying signals through Treg, rank fitting business accounts and optionally enrich authorized business contact information.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-lead-signals\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-lead-signals\u002FSKILL.md",[],[210,211,212,124,94,39,213,214,150,215,216,105,217,218,219,220,221,222,223,135],"signals","lead","lead signals","how","find","ready","as","how to","qualify","people","information","enrich","changes","funding"]