mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 06:36:55 +01:00
Narrow AL development to read-only plan guidance
Retain shared knowledge enrichment and review guidance; defer standalone implementation and source-ingestion tracking. Add runner-owned baseline evidence, contract regressions, and explicit consumer/pilot boundaries. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
f6fca1d56d
commit
1deac52a53
26 changed files with 1486 additions and 11037 deletions
88
.github/scripts/validate_frontmatter.py
vendored
88
.github/scripts/validate_frontmatter.py
vendored
|
|
@ -39,7 +39,6 @@ ACTION_SKILL_REQUIRED_KEYS = {
|
|||
}
|
||||
ACTION_SKILL_OPTIONAL_KEYS = {
|
||||
"bc-version", "technologies", "countries", "application-area", "sub-skills",
|
||||
"quality-skill", "quality-round-limit", "guidance-skill",
|
||||
}
|
||||
META_SKILL_REQUIRED_KEYS = {"kind", "id", "version", "title"}
|
||||
ENTRY_SKILL_REQUIRED_KEYS = {"kind", "id", "version", "title"}
|
||||
|
|
@ -47,10 +46,10 @@ HOST_SKILL_REQUIRED_KEYS = {"name", "description"}
|
|||
|
||||
STANDARD_INPUTS = {
|
||||
"pr-diff", "object-list", "file-path", "repository", "telemetry-query",
|
||||
"development-request", "development-plan",
|
||||
"development-plan",
|
||||
}
|
||||
ALLOWED_OUTPUTS = {
|
||||
"findings-report", "implementation-report", "development-guidance-report",
|
||||
"findings-report", "development-guidance-report",
|
||||
}
|
||||
VALID_SAMPLE_KINDS = {"good", "bad"}
|
||||
|
||||
|
|
@ -410,33 +409,6 @@ def validate_action_skill(path: Path, parsed: Parsed, report: Report) -> None:
|
|||
if bad:
|
||||
report.error(path, "R20", f"invalid sub-skills paths: {bad}", 1)
|
||||
|
||||
if "quality-skill" in fm:
|
||||
quality_skill = fm["quality-skill"]
|
||||
_, err = normalize_repo_md_path(quality_skill)
|
||||
if err:
|
||||
report.error(path, "R31", f"quality-skill {err}", 1)
|
||||
if fm.get("outputs") != ["implementation-report"]:
|
||||
report.error(path, "R31", "quality-skill is valid only with outputs: [implementation-report]", 1)
|
||||
if "quality-round-limit" not in fm:
|
||||
report.error(path, "R31", "quality-skill requires quality-round-limit", 1)
|
||||
|
||||
if "quality-round-limit" in fm:
|
||||
limit = fm["quality-round-limit"]
|
||||
if not isinstance(limit, int) or isinstance(limit, bool) or limit <= 0:
|
||||
report.error(path, "R31", "quality-round-limit must be a positive integer", 1)
|
||||
if "quality-skill" not in fm:
|
||||
report.error(path, "R31", "quality-round-limit requires quality-skill", 1)
|
||||
if fm.get("outputs") != ["implementation-report"]:
|
||||
report.error(path, "R31", "quality-round-limit is valid only with outputs: [implementation-report]", 1)
|
||||
|
||||
if "guidance-skill" in fm:
|
||||
guidance_skill = fm["guidance-skill"]
|
||||
_, err = normalize_repo_md_path(guidance_skill)
|
||||
if err:
|
||||
report.error(path, "R32", f"guidance-skill {err}", 1)
|
||||
if fm.get("outputs") != ["implementation-report"]:
|
||||
report.error(path, "R32", "guidance-skill is valid only with outputs: [implementation-report]", 1)
|
||||
|
||||
# R21 five required sections, in order, each exactly once
|
||||
heads = [h for h, _ in headings_in_order(parsed.body)]
|
||||
indices: list[int] = []
|
||||
|
|
@ -662,58 +634,6 @@ def validate_sub_skills_registry(path: Path, fm: dict[str, Any], root: Path, rep
|
|||
report.error(path, "R26", f"leaf not registered in sub-skills: {leaf}", 1)
|
||||
|
||||
|
||||
def validate_quality_skill(path: Path, fm: dict[str, Any], root: Path, report: Report) -> None:
|
||||
"""R30: implementation quality-skill paths resolve to a findings producer."""
|
||||
quality_skill = fm.get("quality-skill")
|
||||
normalized, err = normalize_repo_md_path(quality_skill)
|
||||
if err or normalized is None:
|
||||
return
|
||||
|
||||
target = root / normalized
|
||||
if not target.is_file():
|
||||
report.error(path, "R30", f"quality-skill does not exist on disk: {normalized}", 1)
|
||||
return
|
||||
if target.resolve() == path.resolve():
|
||||
report.error(path, "R30", "quality-skill must not reference the implementation skill itself", 1)
|
||||
return
|
||||
|
||||
try:
|
||||
target_parsed = parse_markdown(target.read_text(encoding="utf-8"))
|
||||
except UnicodeDecodeError as e:
|
||||
report.error(path, "R30", f"quality-skill is not valid UTF-8: {e}", 1)
|
||||
return
|
||||
target_outputs = (target_parsed.frontmatter or {}).get("outputs")
|
||||
if target_outputs != ["findings-report"]:
|
||||
report.error(path, "R30", f"quality-skill must emit findings-report: {normalized}", 1)
|
||||
|
||||
|
||||
def validate_guidance_skill(path: Path, fm: dict[str, Any], root: Path, report: Report) -> None:
|
||||
"""R33: implementation guidance-skill paths resolve to a read-only planner."""
|
||||
guidance_skill = fm.get("guidance-skill")
|
||||
normalized, err = normalize_repo_md_path(guidance_skill)
|
||||
if err or normalized is None:
|
||||
return
|
||||
|
||||
target = root / normalized
|
||||
if not target.is_file():
|
||||
report.error(path, "R33", f"guidance-skill does not exist on disk: {normalized}", 1)
|
||||
return
|
||||
if target.resolve() == path.resolve():
|
||||
report.error(path, "R33", "guidance-skill must not reference the implementation skill itself", 1)
|
||||
return
|
||||
|
||||
try:
|
||||
target_parsed = parse_markdown(target.read_text(encoding="utf-8"))
|
||||
except UnicodeDecodeError as e:
|
||||
report.error(path, "R33", f"guidance-skill is not valid UTF-8: {e}", 1)
|
||||
return
|
||||
target_fm = target_parsed.frontmatter or {}
|
||||
if target_fm.get("outputs") != ["development-guidance-report"]:
|
||||
report.error(path, "R33", f"guidance-skill must emit development-guidance-report: {normalized}", 1)
|
||||
if "development-plan" not in (target_fm.get("inputs") or []):
|
||||
report.error(path, "R33", f"guidance-skill must accept development-plan: {normalized}", 1)
|
||||
|
||||
|
||||
def run(root: Path) -> Report:
|
||||
report = Report()
|
||||
skill_records: list[SkillRecord] = []
|
||||
|
|
@ -781,11 +701,9 @@ def run(root: Path) -> Report:
|
|||
others = [q.relative_to(root).as_posix() for q in paths if q != p]
|
||||
report.error(p, "R24", f"skill id '{sid}' ({kind}) is not unique; also defined in: {others}")
|
||||
|
||||
# Fourth pass: cross-skill references
|
||||
# Fourth pass: R26 sub-skills registry matches leaf files on disk
|
||||
for path, fm in action_skill_fms:
|
||||
validate_sub_skills_registry(path, fm, root, report)
|
||||
validate_quality_skill(path, fm, root, report)
|
||||
validate_guidance_skill(path, fm, root, report)
|
||||
|
||||
return report
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
name: Validate development coverage
|
||||
name: Validate read-only development guidance
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
|
@ -7,20 +7,19 @@ on:
|
|||
branches: [main]
|
||||
|
||||
jobs:
|
||||
validate-development-coverage:
|
||||
runs-on: ubuntu-latest
|
||||
validate-development-guidance:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Validate Microsoft Learn coverage ledger
|
||||
shell: pwsh
|
||||
run: ./tools/Test-LearnCoverage.ps1 -Root .
|
||||
|
||||
- name: Validate and prepare development fixtures
|
||||
shell: pwsh
|
||||
run: ./tools/Test-DevelopmentFixtures.ps1 -Root . -PrepareDirectory "$env:RUNNER_TEMP/bcquality-development-fixtures"
|
||||
|
||||
- name: Validate and prepare development-guidance fixtures
|
||||
shell: pwsh
|
||||
run: ./tools/Test-DevelopmentGuidanceFixtures.ps1 -Root . -PrepareDirectory "$env:RUNNER_TEMP/bcquality-development-guidance-fixtures"
|
||||
|
||||
- name: Run credential-free guidance evaluator regressions
|
||||
shell: pwsh
|
||||
run: ./tools/Test-DevelopmentGuidanceEvaluator.ps1
|
||||
Loading…
Add table
Add a link
Reference in a new issue