mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 09:26:52 +01:00
* Make domain-skill knowledge discovery index-aware The 6 AL domain review skills and read.md now enumerate candidate articles from the BCQuality knowledge index (knowledge-index.json) instead of opening every file under the domain folder to read its frontmatter. The worklist selection predicate is unchanged (keywords intersect diff tokens, or topic matches a changed object type) - only the discovery source changes, so the same articles are selected. Full article bodies are read only for worklisted entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Reconcile §Source wording with the lean knowledge index The BCQuality filter now emits a lean index whose per-article description is a one-line hint rather than the full verbatim Description. Update the six domain skills' §Source to say the index carries a one-line description hint (keywords, title, and a one-line description) instead of the full description. The worklist selection predicate is unchanged: keywords drive selection and the agent opens worklisted articles in full for their rule bodies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Own the knowledge-index generator in BCQuality The knowledge index is an acceleration of the skills' Source step, and its schema is part of that contract — so BCQuality should own the generator rather than each consumer re-implementing it. Add tools/Build-KnowledgeIndex.ps1 (the parser + lean-description shaping + emit, lifted verbatim from the BCAppsBCQuality filter prototype) and document the index in agent-consumption.md. Consumers prune their clone to policy, then call this script; the index stays in lockstep with the Source contract and every orchestrator gets the same faithful index for free. The worklist selection predicate is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * 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> * Make runtime index build non-interactive and self-contained entry.md now gives the exact build command (pwsh ./tools/Build-KnowledgeIndex.ps1) so the agent's preparation step is unambiguous, and the generator's -BCQualityRoot parameter is optional (defaults to the clone root) so it runs in non-interactive -p mode without prompting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Resolve knowledge-index root to absolute path (cross-platform fix) Get-ChildItem.FullName is always absolute, so deriving the relative article path via Substring(\.Length) requires an absolute root. A relative root such as '.' (used by the CI guard's 'Test-KnowledgeIndex.ps1 -Root .') left the full path almost intact on Linux, producing bogus 'home/runner/.../knowledge' paths and failing the coverage check. Normalise both the generator's -BCQualityRoot and the test's -Root with Resolve-Path before use. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jesper Schulz-Wedde <jesper.schulzwedde@microsoft.com>
178 lines
11 KiB
Markdown
178 lines
11 KiB
Markdown
---
|
|
kind: entry-point
|
|
id: entry
|
|
version: 1
|
|
title: Entry — route a task to the action skill(s) that apply
|
|
---
|
|
|
|
# Entry
|
|
|
|
When an agent is pointed at BCQuality to perform a task, it invokes this skill first. Entry returns a **dispatch record** naming the action skill or skills to invoke next. Routing logic lives here, not in the orchestrator.
|
|
|
|
Entry is its own `kind`: `entry-point`. It structurally follows the DO four-step pattern (Source → Relevance → Worklist → Action) but the units it selects are action skills, not knowledge files, and its output is a dispatch record, not a findings-report.
|
|
|
|
This contract is stable. Changes require a PR approved by both maintainers.
|
|
|
|
## Inputs
|
|
|
|
The agent invokes Entry with a **task context** supplied by the orchestrator:
|
|
|
|
```yaml
|
|
task-context:
|
|
goal: string # free-text description of what needs doing
|
|
inputs-available: # values the orchestrator has ready to pass to a chosen skill
|
|
- pr-diff
|
|
- file-path
|
|
technologies: [al]
|
|
bc-version: 28
|
|
countries: [w1]
|
|
application-area: [finance]
|
|
enabled-layers: [microsoft, community, custom]
|
|
disabled-skills: [] # repo-relative paths the consumer has opted out of
|
|
```
|
|
|
|
`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, from the checkout root:
|
|
|
|
```
|
|
pwsh ./tools/Build-KnowledgeIndex.ps1
|
|
```
|
|
|
|
It defaults to indexing this checkout and writes `knowledge-index.json` at the 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.
|
|
|
|
## Relevance
|
|
|
|
A candidate is relevant when every condition below holds:
|
|
|
|
1. Its frontmatter `kind` is `action-skill`.
|
|
2. `task-context.inputs-available` intersects its declared `inputs` — the orchestrator has at least one of the input types the skill accepts. A skill is NOT required to accept every input the orchestrator can supply; it is the skill's responsibility to return `outcome: "not-applicable"` if the supplied subset is insufficient.
|
|
3. Its frontmatter filter dimensions (`bc-version`, `technologies`, `countries`, `application-area`) match the task context per READ's matching semantics. A dimension omitted from `task-context` is treated as a wildcard and matches any value the skill declares; a dimension explicitly supplied in `task-context` must match the skill's declared values per READ. Conditionally-applicable candidates (any dimension `unknown` per READ) are admitted; they are not filtered out at Entry and are the dispatched skill's concern.
|
|
4. Its repo-relative path is not in `task-context.disabled-skills`.
|
|
|
|
Candidates that fail any condition go to `skipped` with the corresponding reason (`inputs-unsatisfied`, `filter-mismatch`, `configuration`). Skills excluded because they are not `kind: action-skill` are not reported in `skipped`.
|
|
|
|
## Worklist
|
|
|
|
Narrow the relevant set to the skills that will actually be dispatched:
|
|
|
|
1. **Goal match.** Score each candidate's `description` and `id` against `task-context.goal`. Drop candidates that do not plausibly address the goal; record them in `skipped` with `reason: "goal-mismatch"`. Scoring is implementation-defined; agents MUST prefer exact keyword overlap before fuzzy signals.
|
|
2. **Super-skill precedence.** When a super-skill and any skill listed in its `sub-skills` are both in the remaining set, the super-skill supersedes the sub-skill **only when the goal is a broader match for the super-skill than for the sub-skill**. When the goal specifically names a concern the sub-skill handles (for example, goal = *"performance review"* with `al-code-review` and `al-performance-review` both present), the sub-skill wins and the super-skill is dropped with `reason: "narrower-sub-skill-selected"`. Otherwise the super-skill wins and each listed sub-skill in the set is dropped with `reason: "superseded-by-super-skill"`. The principle is: Entry dispatches the narrowest skill that satisfies the goal. A dropped sub-skill's `skipped` entry MUST carry `superseded-by` naming the super-skill that won; a dropped super-skill's entry MUST carry `superseded-by` naming the winning sub-skill.
|
|
3. **Layer precedence.** When two remaining candidates share the same `id` across layers, keep the highest-precedence one. Skill layer precedence is `/custom/` over `/community/` over `/microsoft/` — the same ordering READ defines for knowledge files. Drop the losers with `reason: "layer-precedence"` and `superseded-by` naming the winning path.
|
|
|
|
Each dropped candidate appears in `skipped[]` at most once; record the first reason that caused the drop.
|
|
|
|
The post-filter set is the **dispatch list**.
|
|
|
|
## Action
|
|
|
|
Emit a single JSON document conforming to the output contract below. Entry does not invoke the selected skills — that is the agent's responsibility after receiving the dispatch record.
|
|
|
|
## Output
|
|
|
|
```json
|
|
{
|
|
"skill": { "id": "entry", "version": 1 },
|
|
"outcome": "routed | no-match | failed",
|
|
"outcome-reason": "string",
|
|
"dispatch": [
|
|
{
|
|
"skill": {
|
|
"id": "al-code-review",
|
|
"version": 1,
|
|
"path": "microsoft/skills/review/al-code-review.md"
|
|
},
|
|
"rationale": "string",
|
|
"inputs": ["pr-diff"]
|
|
}
|
|
],
|
|
"skipped": [
|
|
{
|
|
"skill": { "id": "string", "path": "string" },
|
|
"reason": "inputs-unsatisfied | filter-mismatch | goal-mismatch | layer-precedence | superseded-by-super-skill | narrower-sub-skill-selected | configuration",
|
|
"superseded-by": { "id": "string", "path": "string", "version": 1 }
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Field semantics
|
|
|
|
**`outcome`** (required) —
|
|
|
|
- `routed` — `dispatch` is non-empty; the agent proceeds to invoke each listed skill.
|
|
- `no-match` — no action skill applied to the task; `dispatch` is empty. Set `outcome-reason`. Candidates that were considered and dropped MUST appear in `skipped[]`.
|
|
- `failed` — Entry itself could not complete (for example, the action-skill folders could not be enumerated, or `task-context` was malformed). Set `outcome-reason`. Agents MUST NOT synthesize a dispatch in this case. This is distinct from an action skill's own `outcome: "failed"` per DO, which applies during skill execution after dispatch.
|
|
|
|
**`dispatch[]`** — each entry names one action skill to invoke.
|
|
|
|
- `skill.path` — repo-relative, forward slashes. The agent fetches and executes the file directly from this path.
|
|
- `skill.version` — copied from the dispatched skill's frontmatter so the orchestrator can detect drift between dispatch time and execution.
|
|
- `rationale` — short human-readable string, for logs and traceability.
|
|
- `inputs` — the intersection of `task-context.inputs-available` and the skill's declared `inputs`. The agent MUST pass exactly this subset when invoking the skill. Sending a strict intersection avoids accidental information leakage between skills.
|
|
|
|
Ordering of `dispatch[]` is not significant.
|
|
|
|
**`skipped[]`** — MUST list every candidate that was considered and dropped. Each dropped candidate appears at most once; the first drop reason wins. Reasons:
|
|
|
|
- `inputs-unsatisfied` — `task-context.inputs-available` did not intersect the skill's declared `inputs`.
|
|
- `filter-mismatch` — one or more frontmatter filter dimensions explicitly did not match.
|
|
- `goal-mismatch` — Relevance admitted the candidate but it failed the goal-match step.
|
|
- `layer-precedence` — a higher-precedence skill with the same `id` won. `superseded-by` is required.
|
|
- `superseded-by-super-skill` — a super-skill listing this skill as a sub-skill was dispatched instead. `superseded-by` is required.
|
|
- `narrower-sub-skill-selected` — a sub-skill listed by this super-skill was dispatched because the goal specifically matched it. `superseded-by` is required.
|
|
- `configuration` — the skill is listed in `task-context.disabled-skills`.
|
|
|
|
**`superseded-by`** — required for `layer-precedence`, `superseded-by-super-skill`, and `narrower-sub-skill-selected`; omitted otherwise. Names the winning skill by `id`, `path`, and `version`.
|
|
|
|
Empty-dispatch example (no action skills exist in any enabled layer):
|
|
|
|
```json
|
|
{
|
|
"skill": { "id": "entry", "version": 1 },
|
|
"outcome": "no-match",
|
|
"outcome-reason": "No action skills found under */skills/ in the enabled layers.",
|
|
"dispatch": [],
|
|
"skipped": []
|
|
}
|
|
```
|
|
|
|
Populated example (PR review on a repo where only `al-performance-review` is enabled; `al-code-review` and `al-security-review` were disabled by configuration):
|
|
|
|
```json
|
|
{
|
|
"skill": { "id": "entry", "version": 1 },
|
|
"outcome": "routed",
|
|
"dispatch": [
|
|
{
|
|
"skill": { "id": "al-performance-review", "version": 1, "path": "microsoft/skills/review/al-performance-review.md" },
|
|
"rationale": "Goal 'review pull request' matched; inputs-available contains pr-diff.",
|
|
"inputs": ["pr-diff"]
|
|
}
|
|
],
|
|
"skipped": [
|
|
{ "skill": { "id": "al-code-review", "path": "microsoft/skills/review/al-code-review.md" }, "reason": "configuration" },
|
|
{ "skill": { "id": "al-security-review", "path": "microsoft/skills/review/al-security-review.md" }, "reason": "configuration" }
|
|
]
|
|
}
|
|
```
|
|
|
|
## How the agent uses the dispatch
|
|
|
|
1. Invoke Entry with the orchestrator-supplied task context.
|
|
2. Receive the dispatch record.
|
|
3. For each entry in `dispatch[]`, read the referenced action skill, execute its Source → Relevance → Worklist → Action steps per DO, and produce a findings-report.
|
|
4. Return the findings-reports to the orchestrator. When `outcome` is `no-match` or `failed`, return the dispatch record itself so the orchestrator can log the reason.
|
|
|
|
READ and DO are the contracts that govern what the dispatched skills do. An agent that has not yet read READ and DO reads them when it executes the first dispatched skill — they are not prerequisites for invoking Entry.
|