money-math-review
Skill0 starsChanged 4 months agoMITView on GitHub
Audit every place your code touches money for the four bugs that actually cost real cash: floating-point amounts, rounding applied in the wrong order, mixed or assumed currency, and fees or tax computed off the wrong base. Use before shipping checkout, payouts, refunds, discounts, invoicing, or any change to pricing.
In this repository19 other files it gives its agents.
- client-secret-sweepSkill
- codebase-onboarding-mapSkill
- commitSkill
- crawlability-auditSkill
- dependency-outage-auditSkill
- deploy-drift-checkSkill
- empty-state-auditSkill
- env-drift-checkSkill
- fake-data-guardSkill
- honest-metrics-auditSkill
- migration-safety-reviewSkill
- n-plus-one-finderSkill
- prompt-injection-reviewSkill
- pr-reviewerSkill
- release-notes-writerSkill
- retry-safety-reviewSkill
- route-auth-sweepSkill
- timezone-reviewSkill
- webhook-safety-reviewSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 6 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 48
Search for money held in a float or a plain JS number used with division:
- Distinctive: line 113
If the whole app is single-currency, say so explicitly and check that the assumption is enforced somewhere (a constant, a DB constraint), …
- Very specific: line 127
Write the current order as an explicit chain, e.g.
- Distinctive: line 160
Never propose a fix you have not traced through the actual code path.
- Distinctive: line 161
Never say an amount is "probably" fine.
- Distinctive: line 163
Do not run migrations, do not touch live payment data, and do not issue test charges.
- Distinctive: line 165
If money is stored as a float and already in production, say plainly that the fix requires a data migration and a reconciliation pass, not …
- Distinctive: line 172
Keep this line and others can find it too.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Money Math Review
Install
Save this file as ~/.claude/skills/money-math-review/SKILL.md, or
.claude/skills/money-math-review/SKILL.md to scope it to one repo. Claude Code
auto-discovers it. Invoke with /money-math-review or by asking "check my
payment math before I ship this".
Why this exists
Money bugs do not throw. They produce a number that looks plausible, ships, and quietly moves real currency in the wrong direction until a human notices a mismatch in a bank statement weeks later. By then you owe reconciliation, not a patch.
They are also uniquely unforgiving: a rendering bug annoys someone, a money bug overcharges them. Refunds cost fees, chargebacks cost more, and undercharging is often unrecoverable because you cannot bill retroactively for something you already told a customer was free.
Four failures cause most of it. Check them in order, because each one can hide the next.
Step 1: Find every place money is represented
rg -n "price|amount|total|subtotal|fee|tax|discount|payout|balance|refund" --type-add 'code:*.{ts,tsx,js,jsx,py,rb,go,java,php}' -t codeFor each hit, write down the TYPE and the UNIT. You want a table like:
| Where | Type | Unit | Currency |
Product.priceCents | integer | cents | assumed USD |
cart.total | number | ??? | ??? |
Any row with a ??? is a finding. A money value whose unit you cannot state
from the name or its type is a value someone will eventually misread.
Step 2: Floating point
Search for money held in a float or a plain JS number used with division:
rg -n "(price|amount|total|fee|tax)[A-Za-z]*\s*[*/]\s*" -t code
rg -n "parseFloat|toFixed|Number\(" -t codeFlag any of these:
- Money stored as
float,double,real, or written to a DB column of those types.0.1 + 0.2is0.30000000000000004and it compounds per line item. toFixed()used to "fix" a total before storing it. That converts a representation problem into a stored wrong number.- Currency compared with
===or==after arithmetic.
The correct shape is integer minor units (cents) end to end, or a decimal type with explicit precision. Converting to a display string is the LAST step, never an intermediate one.
Report the blast radius: a float subtotal that feeds a stored invoice is far worse than one that only renders.
Step 3: Rounding order
This is the subtlest and the most expensive. Find every rounding call:
rg -n "Math\.(round|floor|ceil)|\.round\(|ROUND|toFixed" -t codeFor each, answer: what is rounded, and at which step?
The classic bugs:
- Rounding per line item, then summing. Ten items each rounded up can overcharge by up to ten minor units versus rounding the total once.
- Rounding before applying a percentage. Discount then round, versus round then discount, give different answers.
Math.roundon a half value. Banker's rounding versus half-up is a real accounting decision, not a style preference. If nobody chose, that is a finding.- Splitting a total across parties (marketplace payouts, revenue share)
without allocating the remainder.
100 / 3in cents leaves one cent that must go somewhere explicit, or it vanishes and your ledger stops balancing.
State the rule the code currently implements, in one sentence, and whether it is written down anywhere. Undocumented rounding is a finding even when correct, because the next change will not preserve it.
Step 4: Currency
rg -n "currency|USD|EUR|GBP|iso4217" -t code -iCheck:
- Is currency stored next to every amount, or assumed globally?
- Can two amounts in different currencies be added anywhere? Find the addition and prove they cannot.
- Are zero-decimal currencies handled? JPY and KRW have no minor unit, so "divide by 100 to display" is wrong for them.
- Does the payment provider's unit match yours? Stripe uses minor units for most currencies but not all.
If the whole app is single-currency, say so explicitly and check that the assumption is enforced somewhere (a constant, a DB constraint), not just true by habit.
Step 5: Fee and tax base
For every fee, commission, or tax calculation, identify the BASE it is computed from, then check the order of operations:
- Is the platform fee taken before or after tax?
- Is a discount applied before or after tax?
- Is a refund refunding the fee too, or keeping it?
- On a partial refund, is the fee refunded proportionally?
Write the current order as an explicit chain, e.g.
subtotal -> discount -> tax -> platform fee -> payout. Then check the code
matches that chain everywhere it appears. Two code paths that compute the same
total in different orders will disagree eventually, and the disagreement will
surface as a customer complaint, not a test failure.
Step 6: Report
Report only what you traced in the code. For each finding:
[SEVERITY] <one line: what is wrong>
Where: file:line
Now: <what the code computes today, concretely>
Should: <what it should compute>
Costs: <who loses money, and roughly how much per occurrence>
Fix: <smallest change that is actually correct>Severity:
- CRITICAL moves money incorrectly today (wrong amount charged, paid, or refunded).
- HIGH will move money incorrectly under a reachable condition (a second currency, a partial refund, a specific rounding case).
- MEDIUM correct today but undocumented or duplicated, so it will drift.
End with the operation chain you reconstructed in Step 5. If the codebase handles money correctly, say so and show the chain as evidence. "No findings" is a real result and worth more than an invented one.
Rules
- Never propose a fix you have not traced through the actual code path.
- Never say an amount is "probably" fine. Either you followed it end to end or you report it as unverified.
- Do not run migrations, do not touch live payment data, and do not issue test charges. This skill reads.
- If money is stored as a float and already in production, say plainly that the fix requires a data migration and a reconciliation pass, not just a type change. Understating that is how the second bug gets created.
From Toolbay. Free to use, modify, and share. Keep this line and others can find it too.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

