bcquality/tools/routing-index.md
dayland 6ed56b95b7 R6 Tier-1: content-owned routing index generator + CI guard
Retire the orchestrator's hardcoded ~28-token signal catalog by compiling a
routing index from article front-matter. New tools/Build-RoutingIndex.ps1 scans
all knowledge layers and emits routing-index.json (signal-token -> domain +
backing articles), seeded from the migrated legacy catalog (routing-seed.json)
so recall is never below today. Optional per-article 'signals:' front-matter
(validator rule R24) is the authored precision path; domain-normalization
reconciles front-matter/orchestrator/feedback domain vocabularies.

Artifact is a runtime build (gitignored), orchestrator-facing only (not the
lean agent-replayed knowledge index). CI guard Test-RoutingIndex.ps1 asserts
determinism, seed recall floor, no orphaned signals, and normalization
completeness; wired into the index workflow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-13 11:20:30 +01:00

113 lines
6 KiB
Markdown

# Routing index
The **routing index** (`routing-index.json`) is the orchestrator-facing companion
to the [knowledge index](../tools/Build-KnowledgeIndex.ps1). Where the knowledge
index lets the *agent* enumerate candidate articles inside the CLI run, the
routing index lets the *orchestrator* decide — deterministically, before any
tokens are spent — which review domains a PR touches and which articles back each
detection signal.
It exists to retire a shadow catalog. The PR-review orchestrator historically
carried a hand-written ~28-token regex catalog (`$BcqSignalCatalog`) hardcoded in
`Invoke-CopilotPRReview.ps1`. That catalog never read the `domain` / `keywords`
front-matter every article already declares, so its blind spots were exactly
where findings were missed. The routing index moves that knowledge into content:
signals and their domains are compiled *from articles*, so a new article — even a
community one — becomes routable the moment it lands, with no orchestrator edit.
## What it is (and is not)
- **It is** an orchestrator-side artifact. It is consumed by `Build-ReviewManifest`
(behind the `BCQ_INDEX_V2` flag) to score per-domain suspicion and shortlist
candidate articles. It is **never** fed into the CLI prompt, so it can be richer
than the lean knowledge index without inflating the token-paid path.
- **It is not** committed. Like `knowledge-index.json`, it is a runtime artifact
rebuilt over each consumer's already-pruned clone (`tools/Build-RoutingIndex.ps1`)
and is `.gitignore`d.
- **It is not** the knowledge index. The two are siblings built from the same
front-matter; the knowledge index is agent-replayed, the routing index is not.
## How signals are compiled
`Build-RoutingIndex.ps1` composes signals from three sources, highest precision
first:
1. **Seed** (`tools/routing-seed.json`) — the migrated legacy catalog. Ingested
first so routing recall is never below what the old hardcoded catalog caught.
Each seed signal is then enriched with **every** article whose (normalized)
domain matches, so recall is complete-by-construction rather than limited by a
hand-maintained token→article map.
2. **Article-declared** (`signals:` front-matter, optional) — the authored,
highest-precision path. Use it when an article catches a specific AL construct
the seed does not name.
3. **Keyword-derived** (`-IncludeKeywordSignals`, off by default) — soft signals
from article keywords at reduced weight. Off by default because keywords are
lowercase-kebab and match noisily, and the online-evals feedback shows the agent
already over-fires. Prefer authored `signals:` for precision.
## The optional `signals:` front-matter block
Knowledge articles may declare an **optional** `signals:` block. When omitted, the
article is still routed via the seed + its domain (fully back-compatible — no
existing article must change). Each entry is either a bare token string or a
mapping:
```yaml
domain: performance
keywords: [lock, readonly, isolation]
signals:
- LockTable # bare token -> pattern \bLockTable\b, domain = article domain
- token: ReadIsolation
pattern: '\bReadIsolation\b' # explicit regex (optional)
domain: performance # override domain (optional; normalized)
```
- `token` (required) — stable signal id; need not be an AL identifier verbatim.
- `pattern` (optional) — regex matched against **added** diff lines. Defaults to
`\b<token>\b`.
- `domain` (optional) — defaults to the article's `domain`, then normalized.
The front-matter validator (`.github/scripts/validate_frontmatter.py`, rule R24)
enforces this shape; `signals` is the only optional key in the otherwise-closed
knowledge key set.
## Domain normalization
Three vocabularies describe the same domains and must be reconciled:
| Front-matter (article) | Orchestrator (`$DomainMap`) | Feedback (`byDomain`) |
|------------------------|-----------------------------|-----------------------|
| `ui` | `Accessibility` | `accessibility` |
| `error-handling` | `Error Handling` | — |
| `web-services` | `Web Services` | — |
| `breaking-changes` | `Breaking Changes` | — |
| `events` / `interfaces`| `Events` / `Interfaces` | — |
| `telemetry` | `Privacy` (folded) | — |
`routing-seed.json → domain-normalization` maps front-matter domains to the
orchestrator's canonical TitleCase taxonomy so seed signals (authored in that
taxonomy) attach to the right articles. `appsource` is a documented **pass-through**
domain (indexed, but has no review leaf skill, so it routes only via the
`al-code-review` super-skill). The CI guard fails if a new front-matter domain
appears that is neither normalized nor a pass-through, keeping the map complete as
content grows.
## Layer-replication story
The whole process is open and per-layer. A partner running the pipeline against
their own `community/` or `custom/` layers gets routing for free: their articles'
front-matter (and any `signals:` they add) compile into the same index by the same
generator. No Microsoft-only data is required to build Tier 1. The later Tier 2
feedback overlay (precision weights distilled from online evals) is a separate,
aggregate-only artifact that multiplies onto these Tier-1 weights — each layer
carries its own overlay produced from its own feedback.
## Files
| File | Role |
|------|------|
| `tools/Build-RoutingIndex.ps1` | Generator (scan layers → `routing-index.json`) |
| `tools/routing-seed.json` | Content-owned seed: migrated catalog + domain-normalization + object-kind hints |
| `tools/routing-index.schema.json` | JSON Schema for the compiled artifact |
| `.github/scripts/Test-RoutingIndex.ps1` | CI guard (determinism, recall floor, no orphans, normalization complete) |
| `.github/workflows/knowledge-index.yml` | Runs both index guards on PR/push to main |