# Keyword Scoring Mechanism in routing.json: How Reverse-Skill Resolves Routing Conflicts

> Understand the keyword scoring mechanism in routing.json. Learn how Reverse-Skill resolves routing conflicts using hit-based scoring and priority rules for efficient route selection.

- Repository: [ZhaoXu/reverse-skill](https://github.com/zhaoxuya520/reverse-skill)
- Tags: deep-dive
- Published: 2026-08-28

---

**The [`routing.json`](https://github.com/zhaoxuya520/reverse-skill/blob/main/routing.json) file uses a hit-based scoring system where each matching keyword rule adds one point to a route's score, resolving ties through a declared `priority` array and falling back to `R0` when no rules match.**

The [`routing.json`](https://github.com/zhaoxuya520/reverse-skill/blob/main/routing.json) configuration file serves as the single source of truth for the reverse-skill task router in the `zhaoxuya520/reverse-skill` repository. Unlike weighted routing systems that assign numeric scores within the configuration, this mechanism uses a simple binary hit-counting approach combined with explicit priority ordering to guarantee deterministic skill selection. Understanding this data-driven approach is essential for customizing routing behavior and debugging conflicts between overlapping keyword patterns.

## How Keyword Rules Trigger Route Scoring

The scoring engine evaluates every incoming query against keyword rules defined in [`skills/config/routing.json`](https://github.com/zhaoxuya520/reverse-skill/blob/main/skills/config/routing.json). Each route contains an array of keyword objects that must satisfy specific matching criteria before contributing to the route's score.

### Rule Matching with Must, Exclude, and MustAll

A keyword rule generates a hit only when all active conditions are satisfied:

- **`must`** – The primary regular expression that must be found in the user query (case-insensitive matching)
- **`exclude`** – An optional pattern that filters out false positives; if matched, the rule is discarded
- **`mustAll`** – An optional array requiring every listed pattern to be present simultaneously

According to the source configuration (lines 5-6), the router processes these conditions sequentially for each keyword object within a route.

### Hit-Based Scoring System

When a keyword rule matches successfully, the associated route receives **exactly one point** added to its score. The file does not store numeric weights—each successful match counts as a single hit regardless of complexity. This design means routes with more granular keyword definitions (multiple `must` clauses across different rules) can accumulate higher scores than generic routes, naturally preferring specific matches over broad ones.

## Conflict Resolution Strategy

After evaluating all routes, the router must select a single primary route when multiple candidates achieve positive scores.

### Priority-Based Tie Breaking

When two or more routes share the highest hit count, the router consults the `priority` array (defined in lines 24-29 of [`routing.json`](https://github.com/zhaoxuya520/reverse-skill/blob/main/routing.json)). The route that appears **first** in this ordered list among the tied candidates is selected as the **PRIMARY** route. This explicit ordering prevents ambiguous routing and ensures deterministic behavior even when keyword overlap occurs between skills.

### Fallback Handling

If no keyword rules match the incoming query, the router defaults to the route specified by `fallbackId` in the `meta` object, which conventionally points to `R0`. This guarantees that every query receives a skill assignment even when the input falls outside defined keyword patterns.

## Implementation Examples

The scoring mechanism is implemented consistently across both JavaScript-like pseudocode and the production PowerShell scripts.

### JavaScript-Style Pseudocode

This implementation mirrors the logic found in the routing engine:

```javascript
// Load routing.json configuration
const routing = require('./skills/config/routing.json');
const priority = routing.priority;
const fallbackId = routing.meta.fallbackId;

function route(query) {
  const hits = {};

  // Scan each route's keyword rules
  for (const [id, info] of Object.entries(routing.routes)) {
    for (const kw of info.keywords) {
      // Evaluate must clause
      if (!new RegExp(kw.must, 'i').test(query)) continue;

      // Check exclude clause
      if (kw.exclude && new RegExp(kw.exclude, 'i').test(query)) continue;

      // Validate mustAll clauses
      if (kw.mustAll) {
        const all = kw.mustAll.every(p => new RegExp(p, 'i').test(query));
        if (!all) continue;
      }

      // Record hit and move to next route
      hits[id] = (hits[id] || 0) + 1;
      break;
    }
  }

  // Determine best route by hit count
  const maxScore = Math.max(...Object.values(hits), 0);
  const candidates = Object.keys(hits).filter(id => hits[id] === maxScore);

  // Resolve ties using priority order
  for (const pid of priority) {
    if (candidates.includes(pid)) return routing.routes[pid];
  }

  // Return fallback route
  return routing.routes[fallbackId];
}

```

### PowerShell Implementation in master-route.ps1

The production entry point `skills/scripts/master-route.ps1` consumes this logic:

```powershell
$routing = Get-Content -Raw 'skills/config/routing.json' | ConvertFrom-Json
$priority = $routing.priority
$fallback = $routing.meta.fallbackId

function Get-Route($query) {
    $hits = @{}
    
    foreach($id in $routing.routes.Keys) {
        foreach($kw in $routing.routes[$id].keywords) {
            if($query -match $kw.must) {
                if($kw.exclude -and $query -match $kw.exclude) { continue }
                
                if($kw.mustAll) {
                    $all = $true
                    foreach($pat in $kw.mustAll) {
                        if(-not ($query -match $pat)) { 
                            $all = $false; 
                            break 
                        }
                    }
                    if(-not $all) { continue }
                }
                
                $hits[$id] = ($hits[$id] ?? 0) + 1
                break
            }
        }
    }
    
    $max = ($hits.Values | Measure-Object -Maximum).Maximum
    $candidates = $hits.Keys | Where-Object { $hits[$_] -eq $max }
    
    foreach($pid in $priority) {
        if($candidates -contains $pid) { 
            return $routing.routes[$pid] 
        }
    }
    
    return $routing.routes[$fallback]
}

```

## Summary

- **Binary Scoring**: Each matching keyword rule contributes exactly one point to a route's score, with no configurable weights in [`routing.json`](https://github.com/zhaoxuya520/reverse-skill/blob/main/routing.json)
- **Explicit Priority**: The `priority` array (lines 24-29) determines the winner when multiple routes achieve identical hit counts
- **Fallback Safety**: The system defaults to `fallbackId` (typically `R0`) when no keyword rules match, ensuring every query receives routing
- **Pattern Flexibility**: Routes support `must`, `exclude`, and `mustAll` patterns to handle complex matching scenarios
- **Data-Driven Design**: All routing logic derives from [`skills/config/routing.json`](https://github.com/zhaoxuya520/reverse-skill/blob/main/skills/config/routing.json), making behavior predictable and version-controllable

## Frequently Asked Questions

### How does routing.json handle multiple keyword matches within the same route?

Each route can contain multiple keyword objects, but only **one hit per route** is recorded regardless of how many keyword rules match. As implemented in `master-route.ps1`, the inner loop breaks after the first successful keyword match, ensuring a route receives a maximum score of one per query evaluation cycle.

### What happens if two routes have the same keyword score?

When scores are tied, the router examines the `priority` array and selects the route that appears **first** in that ordered list. This deterministic resolution prevents random selection and allows administrators to control precedence through configuration rather than adjusting complex regex weights.

### Where is the fallback route configured in routing.json?

The fallback route is defined in the `meta` object at the root of [`routing.json`](https://github.com/zhaoxuya520/reverse-skill/blob/main/routing.json) (lines 5-6) via the `fallbackId` property, which conventionally points to `R0`. If no keyword rules trigger a hit across any route, the system automatically returns this default route.

### Can I assign different numeric weights to keyword rules?

No. The [`routing.json`](https://github.com/zhaoxuya520/reverse-skill/blob/main/routing.json) schema does not support numeric weights. According to the `meta.scoring` field (line 6), the system uses simple hit counting where each successful match adds exactly one point. To prioritize routes, use the `priority` array to establish precedence order rather than attempting to configure decimal scores.