From 5c4bb480c9d1bdbf50032b2451e0d5429b2968bf Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:21:14 +0200 Subject: [PATCH] Own knowledge-index generation in BCQuality (runtime + CI), not the consumer The index is now produced by BCQuality itself: Entry's preparation step rebuilds knowledge-index.json over the live, already-pruned clone at the start of every run, and a new CI workflow validates the generator's health (determinism, full coverage, selection-input integrity). Consumers no longer invoke or know about the index. Rebuilding over the pruned clone (vs shipping a committed full-corpus index) keeps the index exact for any consumer policy: it can never list a denied article, so policy-excluded rules cannot leak into discovery. READ now states the index is discovery-only -- a finding must cite an article opened in full, and rows whose file is absent are discarded before ranking. - skills/entry.md: new 'Preparation -- knowledge index' precondition - skills/read.md: index ownership + discovery-only invariant - microsoft/skills/review/*.md (6): 'BCQuality builds' (not 'the filter emits') - agent-consumption.md 5a: runtime+CI ownership rationale - .github/workflows/knowledge-index.yml + scripts/Test-KnowledgeIndex.ps1: generator guard - .gitignore: never commit the runtime index Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/scripts/Test-KnowledgeIndex.ps1 | 89 +++++++++++++++++++ .github/workflows/knowledge-index.yml | 24 +++++ .gitignore | 4 + agent-consumption.md | 6 +- .../skills/review/al-performance-review.md | 2 +- microsoft/skills/review/al-privacy-review.md | 2 +- microsoft/skills/review/al-security-review.md | 2 +- microsoft/skills/review/al-style-review.md | 2 +- microsoft/skills/review/al-ui-review.md | 2 +- microsoft/skills/review/al-upgrade-review.md | 2 +- skills/entry.md | 9 ++ skills/read.md | 2 +- 12 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 .github/scripts/Test-KnowledgeIndex.ps1 create mode 100644 .github/workflows/knowledge-index.yml diff --git a/.github/scripts/Test-KnowledgeIndex.ps1 b/.github/scripts/Test-KnowledgeIndex.ps1 new file mode 100644 index 0000000..224b010 --- /dev/null +++ b/.github/scripts/Test-KnowledgeIndex.ps1 @@ -0,0 +1,89 @@ +<# +.SYNOPSIS + CI guard for the knowledge-index generator (tools/Build-KnowledgeIndex.ps1). + +.DESCRIPTION + BCQuality owns the knowledge index, so BCQuality CI — not each consumer — + proves the generator is healthy. This script does NOT ship a committed + index that consumers trust at runtime (the index is rebuilt over each + consumer's already-pruned clone by Entry's preparation step, which keeps it + exact for any policy). Instead it asserts the generator itself is sound: + + 1. Determinism — building twice yields byte-identical output once the + volatile `generatedAt` header is normalized. + 2. Coverage — every `*/knowledge/**/*.md` article appears exactly once; + every indexed path exists; no duplicates; no article is dropped. + 3. Selection-input integrity — every parsed article row carries the + non-empty `domain` + `keywords` the worklist predicate selects on, and + every article parses (an unparseable article is an invalid file). + + Exit code 0 = healthy; non-zero = a problem CI must block on. +#> +[CmdletBinding()] +param( + [string] $Root = (Resolve-Path (Join-Path $PSScriptRoot '..' '..')) +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$generator = Join-Path $Root 'tools/Build-KnowledgeIndex.ps1' +if (-not (Test-Path $generator)) { throw "Generator not found: $generator" } + +$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("kbindex_" + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Force -Path $tmp | Out-Null +$idxA = Join-Path $tmp 'a.json' +$idxB = Join-Path $tmp 'b.json' + +$problems = [System.Collections.Generic.List[string]]::new() + +& $generator -BCQualityRoot $Root -IndexPath $idxA | Out-Null +& $generator -BCQualityRoot $Root -IndexPath $idxB | Out-Null + +# 1. Determinism (ignoring the volatile generatedAt timestamp). +$norm = { param($p) ((Get-Content -LiteralPath $p -Raw) -replace '"generatedAt":"[^"]*"', '"generatedAt":""') } +if ((& $norm $idxA) -ne (& $norm $idxB)) { + $problems.Add('Non-deterministic: two builds differ beyond generatedAt.') | Out-Null +} + +$index = Get-Content -LiteralPath $idxA -Raw | ConvertFrom-Json +$rows = @($index.articles) + +# 2. Coverage: one row per knowledge .md, every path real, no duplicates. +$onDisk = @( + foreach ($layer in 'microsoft', 'community', 'custom') { + $kb = Join-Path $Root (Join-Path $layer 'knowledge') + if (Test-Path $kb) { + Get-ChildItem -LiteralPath $kb -Recurse -File -Filter '*.md' | + ForEach-Object { ($_.FullName.Substring($Root.Length).TrimStart([char]'/', [char]'\') -replace '\\', '/') } + } + } +) +if ($rows.Count -ne $onDisk.Count) { + $problems.Add("Coverage mismatch: index has $($rows.Count) rows, disk has $($onDisk.Count) knowledge .md files.") | Out-Null +} +$rowPaths = @($rows | ForEach-Object { $_.path }) +$dupes = @($rowPaths | Group-Object | Where-Object Count -gt 1 | ForEach-Object { $_.Name }) +if ($dupes.Count) { $problems.Add("Duplicate index rows: $($dupes -join ', ')") | Out-Null } +$missingOnDisk = @($rowPaths | Where-Object { -not (Test-Path (Join-Path $Root $_)) }) +if ($missingOnDisk.Count) { $problems.Add("Indexed paths absent on disk: $($missingOnDisk -join ', ')") | Out-Null } +$missingInIndex = @($onDisk | Where-Object { $rowPaths -notcontains $_ }) +if ($missingInIndex.Count) { $problems.Add("Articles missing from index: $($missingInIndex -join ', ')") | Out-Null } + +# 3. Selection-input integrity: parsed rows must carry domain + keywords. +$unparsed = @($rows | Where-Object { -not $_.parsed } | ForEach-Object { $_.path }) +if ($unparsed.Count) { $problems.Add("Unparseable (invalid) articles: $($unparsed -join ', ')") | Out-Null } +foreach ($r in $rows | Where-Object { $_.parsed }) { + if ([string]::IsNullOrWhiteSpace([string]$r.domain)) { $problems.Add("Empty domain: $($r.path)") | Out-Null } + if (-not @($r.keywords).Where({ "$_".Trim() }).Count) { $problems.Add("Empty keywords: $($r.path)") | Out-Null } +} + +Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue + +if ($problems.Count) { + Write-Host "Knowledge-index check FAILED ($($problems.Count) problem(s)):" -ForegroundColor Red + $problems | ForEach-Object { Write-Host " - $_" -ForegroundColor Red } + exit 1 +} +Write-Host "Knowledge-index check PASSED: $($rows.Count) articles, deterministic, full coverage, selection inputs intact." -ForegroundColor Green +exit 0 diff --git a/.github/workflows/knowledge-index.yml b/.github/workflows/knowledge-index.yml new file mode 100644 index 0000000..71acfcb --- /dev/null +++ b/.github/workflows/knowledge-index.yml @@ -0,0 +1,24 @@ +name: Validate knowledge index + +# BCQuality owns the knowledge-index generator (tools/Build-KnowledgeIndex.ps1), +# which the AL review skills' Source step consumes. This guards the generator's +# health so consumers never have to: the index a consumer uses at runtime is +# rebuilt over its own pruned clone, but the generator that builds it lives and +# is validated here. + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + validate-index: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Validate knowledge-index generator + shell: pwsh + run: ./.github/scripts/Test-KnowledgeIndex.ps1 -Root . diff --git a/.gitignore b/.gitignore index f5279e6..1209309 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ node_modules/ # Build artifacts *.log + +# Runtime artifact: the knowledge index is rebuilt over each consumer's +# pruned clone by Entry's preparation step; it is never committed. +/knowledge-index.json diff --git a/agent-consumption.md b/agent-consumption.md index 43e9a48..0d2e5b4 100644 --- a/agent-consumption.md +++ b/agent-consumption.md @@ -54,11 +54,11 @@ At this point the agent reads READ and DO on demand — it needs READ to interpr ### 5a. The knowledge index (Source acceleration) -Discovering candidates at the Source step naively means opening every file under a domain folder just to read its frontmatter `keywords` — on a large corpus that is hundreds of file reads per review. To avoid this, BCQuality emits a **knowledge index**: a single artifact (`knowledge-index.json`) that lists every article surviving the consumer's layer/allow-deny filtering and carries, per article, the exact inputs the Source/Worklist steps consume — `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint. +Discovering candidates at the Source step naively means opening every file under a domain folder just to read its frontmatter `keywords` — on a large corpus that is hundreds of file reads per review. To avoid this, BCQuality maintains a **knowledge index**: a single artifact (`knowledge-index.json`) that lists every article surviving the consumer's layer/allow-deny filtering and carries, per article, the exact inputs the Source/Worklist steps consume — `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint. -The index is **owned by BCQuality**, not by each consumer: its generator (`tools/Build-KnowledgeIndex.ps1`) ships here, next to the skills and knowledge it derives from, so the index schema stays in lockstep with the Source contract and every orchestrator gets the same faithful index for free instead of re-implementing the parser. An orchestrator prunes its clone to policy, then calls `Build-KnowledgeIndex.ps1` against it. +The index is **owned and produced by BCQuality**, not by each consumer: its generator (`tools/Build-KnowledgeIndex.ps1`) ships here, next to the skills and knowledge it derives from, so the index schema stays in lockstep with the Source contract and every consumer gets the same faithful index for free instead of re-implementing the parser. The consuming orchestrator does **not** build or invoke the index — it only prunes its clone to policy as it already does. The index is then (re)generated by BCQuality itself: **Entry's preparation step runs `Build-KnowledgeIndex.ps1` over the live, already-pruned clone** at the start of every run (see `skills/entry.md`), and BCQuality CI (`.github/workflows/knowledge-index.yml`) validates that the generator is healthy and deterministic. Building over the *pruned* clone — rather than shipping a committed full-corpus index that consumers trust — keeps the index exact for any consumer policy: it can never list an article the consumer denied, so policy-excluded rules cannot leak into discovery. -The index changes only *how candidates are discovered*, never *which are selected*. The Worklist predicate is unchanged — `keywords` still drive selection — and the agent still opens each worklisted article **in full** to read its `## Best Practice` / `## Anti Pattern` rule bodies. When no index is present, skills fall back to path-based discovery (collect by domain folder), so review still works. +The index changes only *how candidates are discovered*, never *which are selected*. The Worklist predicate is unchanged — `keywords` still drive selection — and the agent still opens each worklisted article **in full** to read its `## Best Practice` / `## Anti Pattern` rule bodies; the index is discovery metadata only and never substitutes for the article body. When no index is present, skills fall back to path-based discovery (collect by domain folder), so review still works. ### 6. Agent emits structured output The output contract is defined in the DO meta-skill so that every action skill — today's and next year's — produces the same shape: diff --git a/microsoft/skills/review/al-performance-review.md b/microsoft/skills/review/al-performance-review.md index 60f3a67..09bef27 100644 --- a/microsoft/skills/review/al-performance-review.md +++ b/microsoft/skills/review/al-performance-review.md @@ -20,7 +20,7 @@ An orchestrator invokes this skill with either a `pr-diff` (the standard PR-revi ## Source -Read the BCQuality knowledge index once — the `knowledge-index.json` the BCQuality filter emits at the root of the knowledge checkout. It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `performance` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/performance/**`. +Read the BCQuality knowledge index once — the `knowledge-index.json` BCQuality builds at the root of the knowledge checkout (Entry's preparation step regenerates it over the live, already-filtered clone — see `skills/entry.md`). It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `performance` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/performance/**`. ## Relevance diff --git a/microsoft/skills/review/al-privacy-review.md b/microsoft/skills/review/al-privacy-review.md index 2c5c320..4ef87e3 100644 --- a/microsoft/skills/review/al-privacy-review.md +++ b/microsoft/skills/review/al-privacy-review.md @@ -20,7 +20,7 @@ An orchestrator invokes this skill with either a `pr-diff` (the standard PR-revi ## Source -Read the BCQuality knowledge index once — the `knowledge-index.json` the BCQuality filter emits at the root of the knowledge checkout. It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `privacy` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/privacy/**`. +Read the BCQuality knowledge index once — the `knowledge-index.json` BCQuality builds at the root of the knowledge checkout (Entry's preparation step regenerates it over the live, already-filtered clone — see `skills/entry.md`). It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `privacy` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/privacy/**`. ## Relevance diff --git a/microsoft/skills/review/al-security-review.md b/microsoft/skills/review/al-security-review.md index 03d9db5..1e72447 100644 --- a/microsoft/skills/review/al-security-review.md +++ b/microsoft/skills/review/al-security-review.md @@ -20,7 +20,7 @@ An orchestrator invokes this skill with either a `pr-diff` (the standard PR-revi ## Source -Read the BCQuality knowledge index once — the `knowledge-index.json` the BCQuality filter emits at the root of the knowledge checkout. It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `security` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/security/**`. +Read the BCQuality knowledge index once — the `knowledge-index.json` BCQuality builds at the root of the knowledge checkout (Entry's preparation step regenerates it over the live, already-filtered clone — see `skills/entry.md`). It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `security` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/security/**`. ## Relevance diff --git a/microsoft/skills/review/al-style-review.md b/microsoft/skills/review/al-style-review.md index 229b552..d926df3 100644 --- a/microsoft/skills/review/al-style-review.md +++ b/microsoft/skills/review/al-style-review.md @@ -22,7 +22,7 @@ An orchestrator invokes this skill with either a `pr-diff` or a `file-path`. The ## Source -Read the BCQuality knowledge index once — the `knowledge-index.json` the BCQuality filter emits at the root of the knowledge checkout. It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `style` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/style/**`. +Read the BCQuality knowledge index once — the `knowledge-index.json` BCQuality builds at the root of the knowledge checkout (Entry's preparation step regenerates it over the live, already-filtered clone — see `skills/entry.md`). It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `style` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/style/**`. ## Relevance diff --git a/microsoft/skills/review/al-ui-review.md b/microsoft/skills/review/al-ui-review.md index 7e0eb57..ef2e94d 100644 --- a/microsoft/skills/review/al-ui-review.md +++ b/microsoft/skills/review/al-ui-review.md @@ -22,7 +22,7 @@ An orchestrator invokes this skill with either a `pr-diff` or a `file-path`. The ## Source -Read the BCQuality knowledge index once — the `knowledge-index.json` the BCQuality filter emits at the root of the knowledge checkout. It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `ui` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/ui/**`. +Read the BCQuality knowledge index once — the `knowledge-index.json` BCQuality builds at the root of the knowledge checkout (Entry's preparation step regenerates it over the live, already-filtered clone — see `skills/entry.md`). It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `ui` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/ui/**`. ## Relevance diff --git a/microsoft/skills/review/al-upgrade-review.md b/microsoft/skills/review/al-upgrade-review.md index e521fa3..7ccfa4a 100644 --- a/microsoft/skills/review/al-upgrade-review.md +++ b/microsoft/skills/review/al-upgrade-review.md @@ -20,7 +20,7 @@ An orchestrator invokes this skill with either a `pr-diff` (the standard PR-revi ## Source -Read the BCQuality knowledge index once — the `knowledge-index.json` the BCQuality filter emits at the root of the knowledge checkout. It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `upgrade` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/upgrade/**`. +Read the BCQuality knowledge index once — the `knowledge-index.json` BCQuality builds at the root of the knowledge checkout (Entry's preparation step regenerates it over the live, already-filtered clone — see `skills/entry.md`). It lists every article that survived layer and allow/deny filtering and carries, per article, its `path`, `layer`, `domain`, frontmatter dimensions, `keywords`, `title`, and a one-line `description` hint — exactly the fields Relevance and Worklist consume. Take the index entries whose `domain` is `upgrade` as this skill's candidate set across every enabled layer; do not open the individual article files at this step. Open an article's full body only once it enters the Worklist below, so a review reads the index plus the handful of worklisted articles instead of every file under `*/knowledge/upgrade/**`. ## Relevance diff --git a/skills/entry.md b/skills/entry.md index d63ca2e..3433f58 100644 --- a/skills/entry.md +++ b/skills/entry.md @@ -33,6 +33,15 @@ task-context: `goal` and `inputs-available` are required. Filter dimensions (`technologies`, `bc-version`, `countries`, `application-area`) are optional; omitting a dimension is equivalent to "unconstrained" — see Relevance for the exact matching rule. `enabled-layers` defaults to all three. `disabled-skills` defaults to empty. +## Preparation — knowledge index + +Before routing, ensure the knowledge index is current for the **live** clone. The dispatched review skills read `knowledge-index.json` (at the clone root) at their Source step instead of opening every knowledge file — see READ's [Retrieval workflow](read.md). Because a consumer prunes its clone to policy *before* the agent runs, the index MUST be built over the clone as it exists now, so it lists exactly the articles that survived pruning and never an article the consumer denied: + +- If `knowledge-index.json` is absent — or you cannot confirm it reflects the current knowledge tree — regenerate it by running `tools/Build-KnowledgeIndex.ps1`. It walks the knowledge files present in the clone and writes the index at the clone root in well under a second. When in doubt, rebuild: a sub-second rebuild is always cheaper than a stale or over-listing index, which is a correctness risk. +- This is a side step. It MUST NOT change Entry's output — the dispatch record below is the only thing Entry emits, and build logs are never part of the dispatch JSON. + +Generation is **owned by BCQuality**: the generator ships here next to the skills and knowledge it derives from, and the consuming orchestrator neither builds nor knows about the index. + ## Source All action skills under `*/skills/**/*.md` across the layers named in `enabled-layers`. Meta-skills in `/skills/` (including this file) are not candidates and MUST be excluded. Entry never dispatches Entry. diff --git a/skills/read.md b/skills/read.md index 7b29f18..dbeaa63 100644 --- a/skills/read.md +++ b/skills/read.md @@ -140,7 +140,7 @@ Consumers that surface sample code to an end user or agent SHOULD cite the sampl The standard workflow for finding applicable files: -1. Collect candidates from the knowledge index (`knowledge-index.json`) when the consumer provides one: it lists every filtered article with the frontmatter, `keywords`, `title`, and `description` that steps 2-3 need, so candidates are enumerated without opening each file. Absent an index, collect candidates by path (typically by `domain` subfolder, across enabled layers). +1. Collect candidates from the knowledge index (`knowledge-index.json`). BCQuality maintains it: Entry's preparation step (see [entry.md](entry.md)) regenerates it over the live, already-filtered clone, so it lists exactly the articles that survived the consumer's layer/allow-deny pruning, each with the frontmatter, `keywords`, `title`, and one-line `description` that steps 2-3 need — candidates are enumerated without opening each file. The index is **discovery metadata only**: it tells you *which* files to open, it does not substitute for them. A finding MUST cite only an article that was opened and read in full; an index row whose file is absent from the clone MUST be discarded *before* ranking or worklisting, and its metadata MUST NOT seed a finding. Absent an index, collect candidates by path (typically by `domain` subfolder, across enabled layers). 2. Filter by frontmatter using the matching rules above. Files that are not applicable are discarded. 3. Rank or narrow by `keywords` relevance to the task. 4. Resolve conflicts via layer precedence.