# How the Hallmark Audit Verb Scores Code Against Anti-Patterns

> Learn how Hallmark's audit verb scores code against anti-patterns. It parses code into ASTs, matches signatures to anti-patterns, and provides a 0-100 severity score.

- Repository: [Hassan El Mghari/hallmark](https://github.com/Nutlope/hallmark)
- Tags: how-to-guide
- Published: 2026-07-20

---

**The Hallmark `audit` verb evaluates existing code by parsing source files into Abstract Syntax Trees (ASTs), matching structural signatures against a weighted markdown catalogue of anti-patterns, and normalizing the cumulative severity to a 0-100 score.**

The Hallmark CLI provides an `audit` verb that analyzes existing code for structural anti-patterns. According to the Nutlope/hallmark source code, this verb implements a three-stage pipeline defined in [`skills/hallmark/SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md) that loads pattern definitions from [`skills/hallmark/references/anti-patterns.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/anti-patterns.md), traverses AST nodes, and aggregates severity weights into a final quality score.

## Loading the Anti-Pattern Catalogue

The audit process begins by ingesting the anti-pattern catalogue stored at [`skills/hallmark/references/anti-patterns.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/anti-patterns.md). This markdown file defines each anti-pattern with a **signature** describing its AST structure, a **description** for reporting, and a **weight** representing severity.

At runtime, the verb invokes `loadAntiPatternCatalogue()` to parse this file into an in-memory array of pattern objects. The catalogue serves as the single source of truth for what constitutes a code smell, allowing teams to customize rules without modifying core logic.

## Scanning Source Files with AST Parsing

Once the catalogue is loaded, the `audit` handler—registered via `skill.registerVerb('audit', async (args) => { ... })`—collects all source files from the user-provided path using `collectSourceFiles(args.path)`.

For each file, Hallmark generates an AST using `@babel/parser` (for JavaScript and TypeScript) via `parseToAST(file.content)`. The verb then traverses the tree and checks each node against the catalogue signatures using `matchesPattern(ast, pattern.signature)`. When a match occurs, the engine records the file path, line number, pattern weight, and description into a `findings` array.

## Calculating the Final Score

After the scan completes, Hallmark aggregates the results to produce the final metric. The scoring algorithm sums the weights of detected anti-patterns and normalizes them against the theoretical maximum possible score from the catalogue:

```javascript
const totalWeight = findings.reduce((s, f) => s + f.weight, 0);
const maxPossible = catalogue.reduce((s, p) => s + p.weight, 0);
const score = Math.round((totalWeight / maxPossible) * 100);

```

The resulting **0-100 score** interprets 0 as a perfect codebase with no anti-patterns detected, while 100 indicates saturation with high-severity issues. The verb concludes by invoking `reportAuditResult(score, findings)` to output a human-readable report listing each violation, its location, and individual contribution to the total.

## Implementation Example

The core logic resides in [`skills/hallmark/SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md), where the verb registration wires together the catalogue loader, AST parser, and scoring engine:

```javascript
// Excerpt from skills/hallmark/SKILL.md
skill.registerVerb('audit', async (args) => {
  const catalogue = await loadAntiPatternCatalogue();
  const files = await collectSourceFiles(args.path);
  const findings = [];

  for (const file of files) {
    const ast = parseToAST(file.content);
    for (const pattern of catalogue) {
      if (matchesPattern(ast, pattern.signature)) {
        findings.push({
          file: file.path,
          line: locateLine(ast, pattern.signature),
          weight: pattern.weight,
          description: pattern.description,
        });
      }
    }
  }

  const totalWeight = findings.reduce((s, f) => s + f.weight, 0);
  const maxPossible = catalogue.reduce((s, p) => s + p.weight, 0);
  const score = Math.round((totalWeight / maxPossible) * 100);

  reportAuditResult(score, findings);
});

```

## Running the Audit

Execute the verb from the command line by providing a target directory:

```bash
hallmark audit ./src

```

Typical output displays the normalized score followed by a detailed breakdown:

```

Audit Score: 27 / 100

Detected anti-patterns:
  • src/utils/helpers.js:12 – “Deeply nested callbacks” (weight 5)
  • src/components/Widget.tsx:45 – “Excessive prop drilling” (weight 8)
  • src/api/client.ts:78 – “Hard-coded URLs” (weight 4)

```

## Customizing Anti-Patterns

Because the catalogue is markdown-based, extending the audit requires only editing [`skills/hallmark/references/anti-patterns.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/anti-patterns.md). Add a new entry with the required fields:

```markdown

### Unused imports

- **Description:** Files contain imports that are never referenced.
- **Signature:** ImportDeclaration nodes with no Identifier usages.
- **Weight:** 3

```

The next invocation of `hallmark audit` automatically incorporates the new pattern into the scoring calculation without redeploying the CLI.

## Summary

- The **Hallmark audit verb** loads anti-pattern definitions from [`skills/hallmark/references/anti-patterns.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/anti-patterns.md) at runtime.
- It parses source files into **ASTs** using `@babel/parser` and matches nodes against catalogue signatures.
- Scores are **normalized to 0-100** based on the formula `(totalWeight / maxPossible) * 100`, where lower values indicate cleaner code.
- The modular catalogue allows teams to customize rules by editing markdown files rather than source code.

## Frequently Asked Questions

### What file does Hallmark use to define anti-patterns?

Hallmark reads the anti-pattern definitions from [`skills/hallmark/references/anti-patterns.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/anti-patterns.md), a markdown file that maps pattern signatures to severity weights and descriptions. The `loadAntiPatternCatalogue()` function parses this file during the audit initialization phase.

### How does Hallmark calculate the final audit score?

The verb sums the `weight` values of all matched anti-patterns, divides by the sum of all possible weights in the catalogue, and multiplies by 100 to generate a normalized score. The implementation uses `Math.round((totalWeight / maxPossible) * 100)` to produce an integer between 0 and 100.

### Can I add custom anti-patterns to the audit?

Yes. Adding a new entry to [`anti-patterns.md`](https://github.com/Nutlope/hallmark/blob/main/anti-patterns.md) with a `signature` and `weight` field automatically includes it in the next audit run. The `skill.registerVerb` logic in [`SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/SKILL.md) dynamically loads the catalogue, so no changes to the core JavaScript are required.

### Which parser does Hallmark use for JavaScript and TypeScript files?

According to the source implementation in [`skills/hallmark/SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md), Hallmark uses **`@babel/parser`** to generate Abstract Syntax Trees from source code before matching against anti-pattern signatures defined in the catalogue.