@greymoth/cancel-friction-analyzer
v0.1.0
Published
Analysis-only scorer for subscription cancellation friction. Scores a flow you operate (or have consent to assess) against consumer-protection criteria (ROSCA, EU CRD, JP 特商法) and dark-pattern taxonomies, and renders a self-contained HTML scorecard. No th
Maintainers
Readme
cancel-friction-analyzer (cfa)
Score how hard your subscription is to cancel, against consumer-protection criteria (US ROSCA, EU withdrawal rules, JP 特定商取引法) and established dark-pattern taxonomies (Mathur et al., OECD, confirmshaming) — and render a self-contained HTML scorecard you can act on.
Analysis-only. You describe a flow you operate (or have consent to assess). This tool does not crawl third-party sites, name competitors, or publish anything. It produces a self-diagnosis for your own improvement.
Not legal advice. Verdicts are simplified heuristics; regulations change.
Why it exists
The original brief was an automated "attack competitors' cancellation flows and auto-publish takedown LPs" engine — which is defamation / unfair-competition risk and was rejected. This is the clean reshape: turn the same insight (most cancellation flows are needlessly hostile) inward, as a compliance self-check. It is the smallest bet in its program and is built lean accordingly.
Requirements
Node >= 22.18 (native TypeScript type-stripping — no build step).
npm install only for npm run typecheck.
Use
node src/cli.ts score examples/dark-flow.json --html scorecard.html --json report.jsonDescribe your flow as JSON (see examples/):
{
"service": "My App",
"signupOnline": true, "signupSteps": 3, "cancelSteps": 2,
"cancelPhoneOnly": false, "cancelOnlineAvailable": true,
"mandatoryRetentionScreens": 0, "declineButtonText": "Cancel my subscription",
"cancelLinkDepthClicks": 1, "refundOrWithdrawalDisclosed": true,
"hiddenCostsAtCheckout": false, "jurisdictions": ["US", "EU", "JP"]
}Exit code is 1 for a high_friction/dangerous flow (CI gate), 0 otherwise.
Scoring
friendliness = 100 − Σ weighted penalties. Each penalty is a flagged
dark-pattern with a cited basis:
| pattern | basis |
|---|---|
| roach_motel | ROSCA "at least as easy"; CHI'24 |
| phone_only_cancel | ROSCA same-medium; CA/MN 2024 |
| confirmshaming | Brignull; OECD interface interference |
| obstruction_retention | OECD obstruction; FTC negative option |
| hidden_cancel_link | OECD interface interference; EU visibility |
| hidden_costs | Mathur et al.; JP 特商法 §11 |
| no_withdrawal_disclosure | EU CRD; JP 特商法 §11 |
| slow_cancel | CHI'24 time-to-completion |
Bands: ≥80 friendly · 60–79 needs work · 40–59 high friction · <40 dangerous.
Regulatory mapping is honest about flux
The US FTC "Click-to-Cancel" rule was vacated 2025-07-08 and re-proposed via
ANPRM in 2026 — so cfa reports it as uncertain, never pass/fail. ROSCA
(still in force), EU withdrawal button (effective 2026-06-19), and JP 特商法 are
mapped with their current status. Re-verify before relying on any of it.
Test
npm test # 8 tests: scoring, confirmshaming, compliance mapping, scorecard render
npm run typecheckHonest scope
See docs/AUDIT.md. Input is hand-entered by the operator (no automated flow
capture — garbage in, garbage out), the compliance verdicts are simplified, and
this is the weakest bet in its program with no moat. Its only real value is the
convenience of a cited score + a shareable scorecard.
License
MIT. Criteria are cited to public sources (../GitRepo/REFERENCES.md); no code
copied from surveyed dark-pattern detectors.
