How the Hallmark Audit Verb Scores Code Against Anti-Patterns

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 that loads pattern definitions from 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. 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:

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, where the verb registration wires together the catalogue loader, AST parser, and scoring engine:

// 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:

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. Add a new entry with the required fields:


### 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 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, 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 with a signature and weight field automatically includes it in the next audit run. The skill.registerVerb logic in 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, Hallmark uses @babel/parser to generate Abstract Syntax Trees from source code before matching against anti-pattern signatures defined in the catalogue.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →