mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 06:36:55 +01:00
Simplify standalone AL code review skill (#150)
* Simplify standalone AL review skill Rename the host-facing skill to al-code-review, reduce it to a thin Entry adapter, document the architecture, and validate host skill metadata. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: af96bb3d-893a-48a2-8298-c8f4271c162c * Anchor plugin paths at PLUGIN_ROOT and restore the layer-filter caveat The adapter delegated index preparation to Entry's Preparation step, but that step is written for the clone model: it runs `pwsh ./tools/Build-KnowledgeIndex.ps1` from the checkout root. A plugin host's working directory is the user's own project, so the path does not resolve and the index is never built. Because knowledge-index.json is gitignored, a fresh install has none, and READ silently degrades to path-based discovery. The adapter now resolves Entry's repo-relative paths against PLUGIN_ROOT and names the absolute index build; the generator resolves its own root, so it indexes and writes the right tree from any cwd. Entry also asserted that pruning has always happened before it runs, which is false for an installation that ships the whole tree. Entry now scopes that guarantee to consumers that actually prune, and the caveat dropped in the rewrite - that enabled-layers narrows discovery rather than denying access - is restored in the adapter and summarized in the README. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b67b90d-e4b4-4b92-9684-726c72c43b3f --------- Co-authored-by: Jesper Schulz-Wedde <jesper.schulzwedde@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: af96bb3d-893a-48a2-8298-c8f4271c162c Copilot-Session: 0b67b90d-e4b4-4b92-9684-726c72c43b3f
This commit is contained in:
parent
2439d5c412
commit
bca8f478d8
9 changed files with 203 additions and 109 deletions
33
.github/scripts/validate_frontmatter.py
vendored
33
.github/scripts/validate_frontmatter.py
vendored
|
|
@ -42,6 +42,7 @@ ACTION_SKILL_OPTIONAL_KEYS = {
|
|||
}
|
||||
META_SKILL_REQUIRED_KEYS = {"kind", "id", "version", "title"}
|
||||
ENTRY_SKILL_REQUIRED_KEYS = {"kind", "id", "version", "title"}
|
||||
HOST_SKILL_REQUIRED_KEYS = {"name", "description"}
|
||||
|
||||
STANDARD_INPUTS = {
|
||||
"pr-diff", "object-list", "file-path", "repository", "telemetry-query",
|
||||
|
|
@ -444,10 +445,36 @@ def validate_entry_skill(path: Path, parsed: Parsed, report: Report) -> None:
|
|||
report.error(path, "R23", f"version must be a positive integer: {v!r}", 1)
|
||||
|
||||
|
||||
def validate_host_skill(path: Path, parsed: Parsed, report: Report) -> None:
|
||||
if parsed.frontmatter_error:
|
||||
report.error(path, "R01", parsed.frontmatter_error, 1)
|
||||
return
|
||||
fm = parsed.frontmatter
|
||||
assert fm is not None
|
||||
missing = HOST_SKILL_REQUIRED_KEYS - fm.keys()
|
||||
if missing:
|
||||
report.error(path, "R29", f"missing required host-skill keys: {sorted(missing)}", 1)
|
||||
|
||||
name = fm.get("name")
|
||||
if not isinstance(name, str) or not name:
|
||||
report.error(path, "R29", "host-skill name must be a non-empty string", 1)
|
||||
else:
|
||||
if len(name) > 64 or not KEBAB_CASE.fullmatch(name):
|
||||
report.error(path, "R29", f"host-skill name must be lowercase kebab-case and at most 64 characters: '{name}'", 1)
|
||||
if name != path.parent.name:
|
||||
report.error(path, "R29", f"host-skill name must match parent directory '{path.parent.name}', got '{name}'", 1)
|
||||
|
||||
description = fm.get("description")
|
||||
if not isinstance(description, str) or not description:
|
||||
report.error(path, "R29", "host-skill description must be a non-empty string", 1)
|
||||
elif len(description) > 1024:
|
||||
report.error(path, "R29", "host-skill description must be at most 1024 characters", 1)
|
||||
|
||||
|
||||
# --- Path and sample checks -------------------------------------------------
|
||||
|
||||
def classify(path_from_root: Path) -> str | None:
|
||||
"""Return 'knowledge' | 'action-skill' | 'meta' | 'entry' | None."""
|
||||
"""Return 'knowledge' | 'action-skill' | 'host-skill' | 'meta' | 'entry' | None."""
|
||||
parts = path_from_root.parts
|
||||
if len(parts) < 2:
|
||||
return None
|
||||
|
|
@ -459,6 +486,8 @@ def classify(path_from_root: Path) -> str | None:
|
|||
return "entry"
|
||||
if name in META_SKILL_FILES:
|
||||
return "meta"
|
||||
if len(parts) == 3 and parts[2] == "SKILL.md":
|
||||
return "host-skill"
|
||||
return None
|
||||
if top in LAYERS and path_from_root.suffix == ".md":
|
||||
if len(parts) >= 3 and parts[1] == "skills":
|
||||
|
|
@ -616,6 +645,8 @@ def run(root: Path) -> Report:
|
|||
validate_entry_skill(path, parsed, report)
|
||||
if parsed.frontmatter and isinstance(parsed.frontmatter.get("id"), str):
|
||||
skill_records.append(SkillRecord(path, "entry-point", parsed.frontmatter["id"]))
|
||||
elif kind == "host-skill":
|
||||
validate_host_skill(path, parsed, report)
|
||||
|
||||
# Second pass: sample files per knowledge domain
|
||||
for layer in LAYERS:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue