๐Ÿงช
Coverage
25 pts
๐Ÿ”€
Complexity
20 pts
๐Ÿ“
Docs
15 pts
๐Ÿšจ
Error Handling
20 pts
๐Ÿท๏ธ
Type Safety
10 pts
๐Ÿ›ก๏ธ
Security
10 pts
Composite Score Formula
score = (coverage ร— 0.25) + (complexity ร— 0.20) + (error_handling ร— 0.20) + (docs ร— 0.15) + (type_safety ร— 0.10) + (security ร— 0.10)
# All inputs normalized 0โ€“100. Score is always an integer.
๐Ÿงช
Test Coverage
Are your code paths exercised by automated tests?
max 25 pts
Line coverage โ†’ 50% 12 / 25
Branch coverage โ†’ 80% 20 / 25

Ratchet instruments your test runner (Jest, Vitest, pytest, Go test) and collects Istanbul/coverage.py output. Branch coverage is weighted 1.4ร— over line coverage to reward tests that exercise conditional logic.

โœ— before โ€” no test 0 / 25
export function divide(a, b) {
  return a / b;
  // division by zero?
  // NaN? Infinity? nobody knows.
}
โœ“ after โ€” branches covered 23 / 25
export function divide(a, b) {
  if (b === 0) throw new Error('div/0');
  return a / b;
}

// divide.test.ts
it('throws on zero', () =>
  expect(() => divide(1,0)).toThrow());
it('returns quotient', () =>
  expect(divide(10,2)).toBe(5));
๐Ÿ”€
Complexity
Cyclomatic complexity and cognitive load per function
max 20 pts
Avg cyclomatic complexity โ‰ค 5 20 / 20

Ratchet uses the McCabe cyclomatic complexity metric. A score of 1 = no branches. Each if, &&, ||, loop, or catch adds 1. Functions above 10 receive zero complexity points and trigger a quick-fix suggestion to extract sub-functions.

โœ— before โ€” complexity 14 2 / 20
function processOrder(order) {
  if (order.type === 'digital') {
    if (order.paid) {
      if (order.email) {
        try {
          sendEmail(order.email);
          if (order.coupon) {
            applyCoupon(order);
          }
        } catch(e) { /* ... */ }
      }
    }
  } else if (order.type === 'physical') {
    // ... 40 more lines
  }
}
โœ“ after โ€” complexity 3 each 19 / 20
function fulfillDigital(order) {
  if (!order.paid || !order.email) return;
  try {
    sendEmail(order.email);
    if (order.coupon) applyCoupon(order);
  } catch(e) { log(e); }
}

function processOrder(order) {
  const handlers = {
    digital: fulfillDigital,
    physical: fulfillPhysical,
  };
  handlers[order.type]?.(order);
}
๐Ÿ“
Documentation
JSDoc / docstring coverage on public-facing exports
max 15 pts
Public exports documented โ†’ 60% 9 / 15

Only exported symbols (functions, classes, types) count. Internal helpers are excluded to avoid documentation noise. Ratchet checks for a summary sentence, parameter descriptions, and a return type annotation.

โœ— before โ€” no docs 0 / 15
export function formatCurrency(
  amount,
  currency,
  locale
) {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency,
  }).format(amount);
}
โœ“ after โ€” full JSDoc 14 / 15
/**
 * Format a number as a locale-aware
 * currency string.
 * @param amount  - Numeric value
 * @param currency - ISO 4217 code
 * @param locale  - BCP 47 locale tag
 * @returns Formatted string, e.g. "$1,234.56"
 */
export function formatCurrency(
  amount: number,
  currency: string,
  locale: string
): string { /* ... */ }
๐Ÿšจ
Error Handling
Are failures caught, typed, and surfaced correctly?
max 20 pts
Bare catch blocks eliminated 16 / 20

Ratchet penalizes empty catch blocks, swallowed errors, and unhandled promise rejections. It rewards typed error discrimination (using instanceof or error codes), explicit re-throws, and boundary-level fallbacks.

โœ— before โ€” swallowed 3 / 20
async function fetchUser(id) {
  try {
    const res = await api.get(`/users/${id}`);
    return res.data;
  } catch (e) {
    // TODO: handle this later
    return null;
  }
}
โœ“ after โ€” typed + re-thrown 19 / 20
async function fetchUser(id: string)
    : Promise<User> {
  try {
    const res = await api.get(`/users/${id}`);
    return parseUser(res.data);
  } catch (e) {
    if (e instanceof NotFoundError) return null;
    logger.error('fetchUser failed', { id, e });
    throw e;
  }
}
๐Ÿท๏ธ
Type Safety
Explicit types on function signatures and key variables
max 10 pts
Typed function signatures โ†’ 70% 7 / 10

For TypeScript projects, Ratchet counts any escapes, missing return types, and unannotated function parameters. For Python, it checks mypy --strict compliance. Dynamically-typed projects (vanilla JS) are evaluated on JSDoc type annotations instead.

โœ— before โ€” any escapes 2 / 10
function transform(data: any): any {
  const result: any = {};
  for (const key in data) {
    result[key] = data[key];
  }
  return result;
}
โœ“ after โ€” fully typed 10 / 10
function transform<T extends Record
    <string, unknown>>(data: T): T {
  const result = {} as T;
  for (const key in data) {
    result[key] = data[key];
  }
  return result;
}
๐Ÿ›ก๏ธ
Security
Static analysis for OWASP Top 10 and secret exposure
max 10 pts
No high-severity findings 10 / 10

Ratchet integrates with Semgrep (open rules set) to detect SQL injection, XSS, hardcoded credentials, unsafe deserialization, and path traversal. Each high-severity finding deducts 3 pts; medium findings deduct 1 pt. Security score floors at 0.

โœ— before โ€” SQL injection 0 / 10
async function getUser(username) {
  const sql = `SELECT * FROM users
    WHERE username='${username}'`;
  return db.query(sql);
  // username = "' OR '1'='1"
}
โœ“ after โ€” parameterized 10 / 10
async function getUser(
  username: string
): Promise<User | null> {
  return db.query(
    'SELECT * FROM users WHERE username=?',
    [username]
  );
}

Grade Scale

Your composite score maps to a letter grade displayed in ratchet scan output and CI badges.

Grade Score Range Description
S 90 โ€“ 100 Exceptional. Ship it with confidence.
A 80 โ€“ 89 Production-ready. Minor gaps only.
B 65 โ€“ 79 Good baseline. Worth a quick-fix pass.
C 50 โ€“ 64 Needs attention. Run ratchet fix.
D 35 โ€“ 49 Significant debt. Prioritize this sprint.
F 0 โ€“ 34 Critical issues. Block merges until resolved.
Security & Privacy โ†’