From 6fd6399ed2047ee8796193fa6226626a9b322fb9 Mon Sep 17 00:00:00 2001 From: yaojingang Date: Tue, 16 Jun 2026 22:10:00 +0800 Subject: [PATCH] Extract Review Studio gate contract helpers --- reports/architecture_maintainability.json | 22 +++--- reports/architecture_maintainability.md | 10 +-- reports/context_budget.json | 20 +++--- reports/context_budget.md | 2 +- reports/context_budget_summary.json | 10 +-- reports/python_compatibility.json | 8 ++- reports/python_compatibility.md | 2 +- reports/security_trust_report.json | 30 ++++++-- reports/security_trust_report.md | 11 +-- reports/skill-os-2-review.md | 2 +- scripts/review_studio_gate_contract.py | 82 ++++++++++++++++++++++ scripts/review_studio_gates.py | 84 +++-------------------- tests/verify_trust_check.py | 2 + 13 files changed, 165 insertions(+), 120 deletions(-) create mode 100644 scripts/review_studio_gate_contract.py diff --git a/reports/architecture_maintainability.json b/reports/architecture_maintainability.json index d1347d1..1e05115 100644 --- a/reports/architecture_maintainability.json +++ b/reports/architecture_maintainability.json @@ -4,10 +4,10 @@ "generated_at": "2026-06-16", "skill_dir": ".", "summary": { - "python_file_count": 205, - "script_file_count": 132, + "python_file_count": 206, + "script_file_count": 133, "test_file_count": 73, - "internal_module_count": 48, + "internal_module_count": 49, "cli_script_count": 86, "command_handler_count": 68, "entrypoint_command_handler_count": 18, @@ -15,20 +15,13 @@ "warn_line_threshold": 900, "watch_line_threshold": 720, "block_line_threshold": 1500, - "largest_file_lines": 707, + "largest_file_lines": 706, "watchlist_count": 0, "hotspot_count": 0, "blocker_count": 0, "decision": "pass" }, "largest_files": [ - { - "path": "scripts/review_studio_gates.py", - "lines": 707, - "kind": "internal-module", - "severity": "pass", - "recommendation": "Watch this file before adding new responsibilities; extract a helper module when one concern dominates." - }, { "path": "scripts/apply_adaptation.py", "lines": 706, @@ -92,6 +85,13 @@ "severity": "pass", "recommendation": "Watch this file before adding new responsibilities; extract a helper module when one concern dominates." }, + { + "path": "scripts/review_studio_gates.py", + "lines": 643, + "kind": "internal-module", + "severity": "pass", + "recommendation": "Watch this file before adding new responsibilities; extract a helper module when one concern dominates." + }, { "path": "scripts/cross_packager.py", "lines": 638, diff --git a/reports/architecture_maintainability.md b/reports/architecture_maintainability.md index 7dd67cb..07f7988 100644 --- a/reports/architecture_maintainability.md +++ b/reports/architecture_maintainability.md @@ -5,15 +5,15 @@ Generated at: `2026-06-16` ## Summary - decision: `pass` -- python files: `205` -- scripts: `132` +- python files: `206` +- scripts: `133` - tests: `73` -- internal modules: `48` +- internal modules: `49` - CLI scripts: `86` - Yao CLI command handlers: `68` - entrypoint command handlers: `18` - command modules: `6` -- largest file lines: `707` +- largest file lines: `706` - watch threshold lines: `720` - watchlist: `0` - hotspots: `0` @@ -33,7 +33,6 @@ No near-threshold files found. | File | Lines | Kind | Severity | | --- | ---: | --- | --- | -| `scripts/review_studio_gates.py` | `707` | `internal-module` | `pass` | | `scripts/apply_adaptation.py` | `706` | `cli-script` | `pass` | | `tests/verify_yao_cli.py` | `696` | `test` | `pass` | | `scripts/world_class_evidence_contract.py` | `686` | `internal-module` | `pass` | @@ -43,6 +42,7 @@ No near-threshold files found. | `scripts/render_skill_os2_coverage.py` | `649` | `cli-script` | `pass` | | `scripts/render_review_studio.py` | `647` | `cli-script` | `pass` | | `scripts/render_reference_synthesis.py` | `644` | `cli-script` | `pass` | +| `scripts/review_studio_gates.py` | `643` | `internal-module` | `pass` | | `scripts/cross_packager.py` | `638` | `cli-script` | `pass` | | `scripts/build_skill_atlas.py` | `637` | `cli-script` | `pass` | diff --git a/reports/context_budget.json b/reports/context_budget.json index 6249c54..bc82b2a 100644 --- a/reports/context_budget.json +++ b/reports/context_budget.json @@ -6,16 +6,16 @@ "context_budget_tier": "production", "context_budget_limit": 1000, "skill_body_tokens": 797, - "other_text_tokens": 1052134, + "other_text_tokens": 1052511, "estimated_initial_load_tokens": 990, - "estimated_total_text_tokens": 1052931, - "deferred_resource_tokens": 487365, + "estimated_total_text_tokens": 1053308, + "deferred_resource_tokens": 487478, "deferred_resource_warn_threshold": 120000, "deferred_resource_dirs": [ { "path": "scripts", - "estimated_tokens": 427267, - "file_count": 132 + "estimated_tokens": 427380, + "file_count": 133 }, { "path": "references", @@ -36,8 +36,8 @@ "large_deferred_resource_dirs": [ { "path": "scripts", - "estimated_tokens": 427267, - "file_count": 132 + "estimated_tokens": 427380, + "file_count": 133 } ], "deferred_resource_governance": { @@ -59,14 +59,14 @@ ], "missing": [], "path": "scripts", - "estimated_tokens": 427267, - "file_count": 132, + "estimated_tokens": 427380, + "file_count": 133, "rationale": "Script resources are deterministic deferred tools, not initial-load prompt context." } ], "summary": "Large deferred resources are indexed and backed by evidence." }, - "relevant_file_count": 656, + "relevant_file_count": 658, "unused_resource_dirs": [], "quality_signal_points": 130, "quality_density": 131.3 diff --git a/reports/context_budget.md b/reports/context_budget.md index 402a30a..f065f48 100644 --- a/reports/context_budget.md +++ b/reports/context_budget.md @@ -2,7 +2,7 @@ | Target | Path | Tier | Limit | Initial | SKILL | Deferred | Resource Governance | Large Deferred Dirs | Quality Density | Unused Dirs | Status | | --- | --- | --- | ---: | ---: | ---: | ---: | --- | --- | ---: | --- | --- | -| root | `.` | `production` | 1000 | 990 | 797 | 487365 | `governed` | scripts:427267 | 131.3 | - | ok | +| root | `.` | `production` | 1000 | 990 | 797 | 487478 | `governed` | scripts:427380 | 131.3 | - | ok | | complex-release-orchestrator | `examples/complex-release-orchestrator/generated-skill` | `production` | 1000 | 790 | 718 | 1657 | `not-required` | - | 164.6 | - | ok | | governed-incident-command | `examples/governed-incident-command/generated-skill` | `production` | 1000 | 760 | 658 | 1030 | `not-required` | - | 171.1 | - | ok | diff --git a/reports/context_budget_summary.json b/reports/context_budget_summary.json index fcbf1ec..923386b 100644 --- a/reports/context_budget_summary.json +++ b/reports/context_budget_summary.json @@ -8,12 +8,12 @@ "budget_limit": 1000, "initial_tokens": 990, "skill_body_tokens": 797, - "deferred_resource_tokens": 487365, + "deferred_resource_tokens": 487478, "large_deferred_resource_dirs": [ { "path": "scripts", - "estimated_tokens": 427267, - "file_count": 132 + "estimated_tokens": 427380, + "file_count": 133 } ], "deferred_resource_governance": { @@ -35,8 +35,8 @@ ], "missing": [], "path": "scripts", - "estimated_tokens": 427267, - "file_count": 132, + "estimated_tokens": 427380, + "file_count": 133, "rationale": "Script resources are deterministic deferred tools, not initial-load prompt context." } ], diff --git a/reports/python_compatibility.json b/reports/python_compatibility.json index 90ba9b5..79f6697 100644 --- a/reports/python_compatibility.json +++ b/reports/python_compatibility.json @@ -5,7 +5,7 @@ "root": ".", "summary": { "target_python": "3.11", - "file_count": 208, + "file_count": 209, "issue_count": 0, "syntax_error_count": 0, "fstring_311_violation_count": 0, @@ -544,6 +544,12 @@ "issue_count": 0, "issues": [] }, + { + "path": "scripts/review_studio_gate_contract.py", + "ok": true, + "issue_count": 0, + "issues": [] + }, { "path": "scripts/review_studio_gates.py", "ok": true, diff --git a/reports/python_compatibility.md b/reports/python_compatibility.md index a744a0a..4e955b1 100644 --- a/reports/python_compatibility.md +++ b/reports/python_compatibility.md @@ -6,7 +6,7 @@ Generated at: `2026-06-16` - decision: `pass` - target python: `3.11` -- files scanned: `208` +- files scanned: `209` - issues: `0` - syntax errors: `0` - f-string 3.11 violations: `0` diff --git a/reports/security_trust_report.json b/reports/security_trust_report.json index e9b4e43..c48d516 100644 --- a/reports/security_trust_report.json +++ b/reports/security_trust_report.json @@ -2,9 +2,9 @@ "ok": true, "skill_dir": ".", "summary": { - "scanned_files": 223, - "script_count": 132, - "internal_module_count": 46, + "scanned_files": 224, + "script_count": 133, + "internal_module_count": 47, "secret_findings": 0, "dependency_files": [ "requirements-ci.txt" @@ -22,8 +22,8 @@ "help_smoke_failed_count": 0, "interactive_script_count": 0, "package_hash_scope": "source-contract-without-generated-reports", - "package_hash_file_count": 223, - "package_sha256": "9b29a4cc28ab3fb1df2434b22e26b9e92ee04c096f35da350705c4c92307260e" + "package_hash_file_count": 224, + "package_sha256": "c74c96a2bc7dfd7e12c89cb475a6853fcd012bcfe7b4a3905007b896ed2cf957" }, "failures": [], "warnings": [], @@ -1222,6 +1222,20 @@ "network_urls": [], "network_hosts": [] }, + { + "path": "scripts/review_studio_gate_contract.py", + "interface": "internal-module", + "interface_declared": true, + "interface_reason": "Imported by review_studio_gates.py for stable gate scoring and contract checks.", + "has_argparse": false, + "has_main_guard": false, + "uses_input": false, + "uses_network": false, + "uses_file_write": false, + "uses_subprocess": false, + "network_urls": [], + "network_hosts": [] + }, { "path": "scripts/review_studio_gates.py", "interface": "internal-module", @@ -1929,7 +1943,7 @@ "checked_count": 86, "passed_count": 86, "failed_count": 0, - "skipped_count": 46, + "skipped_count": 47, "failed_scripts": [], "results": [ { @@ -2846,6 +2860,10 @@ "path": "scripts/review_studio_formatting.py", "reason": "internal module" }, + { + "path": "scripts/review_studio_gate_contract.py", + "reason": "internal module" + }, { "path": "scripts/review_studio_gates.py", "reason": "internal module" diff --git a/reports/security_trust_report.md b/reports/security_trust_report.md index b143488..25cca9d 100644 --- a/reports/security_trust_report.md +++ b/reports/security_trust_report.md @@ -1,9 +1,9 @@ # Security Trust Report - OK: `True` -- Scanned files: `223` -- Scripts: `132` -- Internal script modules: `46` +- Scanned files: `224` +- Scripts: `133` +- Internal script modules: `47` - Secret findings: `0` - Network-capable scripts: `3` - Network policy covered scripts: `3` @@ -15,8 +15,8 @@ - CLI help smoke failures: `0` - Interactive scripts: `0` - Package hash scope: `source-contract-without-generated-reports` -- Package hash files: `223` -- Package SHA256: `9b29a4cc28ab3fb1df2434b22e26b9e92ee04c096f35da350705c4c92307260e` +- Package hash files: `224` +- Package SHA256: `c74c96a2bc7dfd7e12c89cb475a6853fcd012bcfe7b4a3905007b896ed2cf957` ## Failures @@ -146,6 +146,7 @@ | scripts/review_studio_actions.py | internal-module | True | False | False | False | False | False | False | Imported by render_review_studio.py to keep Review Studio action guidance and source refs out of HTML rendering. | | scripts/review_studio_data.py | internal-module | True | False | False | False | False | False | False | Imported by render_review_studio.py to load Review Studio source reports and metric cards. | | scripts/review_studio_formatting.py | internal-module | True | False | False | False | False | False | False | Imported by render_review_studio.py to format report dictionaries as audit UI panels. | +| scripts/review_studio_gate_contract.py | internal-module | True | False | False | False | False | False | False | Imported by review_studio_gates.py for stable gate scoring and contract checks. | | scripts/review_studio_gates.py | internal-module | True | False | False | False | False | False | False | Imported by render_review_studio.py to keep Review Studio gate evaluation separate from HTML rendering. | | scripts/review_studio_layout.py | internal-module | True | False | False | False | False | False | False | Imported by render_review_studio.py to keep Review Studio layout and CSS out of gate logic. | | scripts/review_studio_output_review.py | internal-module | True | False | False | False | False | False | False | Imported by render_review_studio.py to render output review checklist cards. | diff --git a/reports/skill-os-2-review.md b/reports/skill-os-2-review.md index a518b4a..61bc001 100644 --- a/reports/skill-os-2-review.md +++ b/reports/skill-os-2-review.md @@ -127,7 +127,7 @@ Next move: add real client or installer permission enforcement integration. | Output Eval | `5` cases, with-skill pass rate `100`, baseline pass rate `0`, with file-backed, near-neighbor, boundary coverage, `10` local command-runner execution runs, `0` recorded fixture runs, `0` provider model-executed runs in root release evidence, `10` estimated token counts, provider runner v0 available, `5` blind A/B review pairs, a generated `reports/output_review_decisions.json` template, `0 / 5` reviewer decisions pending, `0` answer keys revealed, and `5` pending answers hidden | | Runtime Conformance | `5 / 5` targets passing | | Target Compiler | `5 / 5` compiled target contracts generated for OpenAI, Claude, generic, Agent Skills compatible, and VS Code / Copilot outputs, including target permission contracts and target-native behavior contracts | -| Trust | `0` secret findings, `1` pinned dependency file, `46` declared internal modules, `3 / 3` network-capable scripts covered by bounded host policy, `86 / 86` CLI help smoke checks passing across `132` scripts, source-contract hash scope explicit | +| Trust | `0` secret findings, `1` pinned dependency file, `47` declared internal modules, `3 / 3` network-capable scripts covered by bounded host policy, `86 / 86` CLI help smoke checks passing across `133` scripts, source-contract hash scope explicit | | Permission Governance | `3 / 3` required high-permission capabilities approved, `0` missing, `0` invalid, `0` expired | | Runtime Permission Probes | `4 / 4` target adapters probed, `0` native-enforcement adapters, `4` explicit metadata fallbacks, `4` residual risks retained for reviewer visibility | | Skill Atlas | `12` scanned skills, `1` actionable root skill, `1` telemetry report, `0` actionable route collisions, `0` actionable owner gaps, `0` actionable stale skills, `0` actionable drift signals, `24` scoped non-actionable issue signals retained for visibility | diff --git a/scripts/review_studio_gate_contract.py b/scripts/review_studio_gate_contract.py new file mode 100644 index 0000000..d1ee04b --- /dev/null +++ b/scripts/review_studio_gate_contract.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Review Studio gate keys, scoring, and contract checks.""" + +from collections import Counter +from typing import Any + + +SCRIPT_INTERFACE = "internal-module" +SCRIPT_INTERFACE_REASON = "Imported by review_studio_gates.py for stable gate scoring and contract checks." + + +GATE_WEIGHTS = { + "trigger-lab": 15, + "output-lab": 20, + "context-budget": 10, + "runtime-matrix": 10, + "trust-report": 10, + "python-compat": 10, + "architecture-maintainability": 10, + "permission-gates": 10, + "permission-runtime": 10, + "skill-atlas": 10, + "operations-loop": 10, + "review-waivers": 10, + "world-class-evidence": 10, + "registry-audit": 10, + "release-notes": 10, + "intent-canvas": 10, +} +REVIEW_STUDIO_GATE_KEYS = frozenset(GATE_WEIGHTS) + + +def status_label(status: str) -> str: + return {"pass": "通过", "warn": "关注", "block": "阻断"}.get(status, status) + + +def add_blockers_from_gate(gates: list[dict[str, str]]) -> tuple[list[dict[str, str]], list[dict[str, str]]]: + blockers = [item for item in gates if item["status"] == "block"] + warnings = [item for item in gates if item["status"] == "warn"] + return blockers, warnings + + +def gate_contract(gates: list[dict[str, str]]) -> dict[str, Any]: + rendered_gate_keys = [str(item.get("key", "")) for item in gates] + rendered_gate_set = set(rendered_gate_keys) + expected_gate_set = set(REVIEW_STUDIO_GATE_KEYS) + duplicate_gate_keys = sorted(key for key, count in Counter(rendered_gate_keys).items() if count > 1) + missing_gate_keys = sorted(expected_gate_set - rendered_gate_set) + unknown_gate_keys = sorted(rendered_gate_set - expected_gate_set) + unweighted_gate_keys = sorted(rendered_gate_set - set(GATE_WEIGHTS)) + return { + "ok": not (missing_gate_keys or unknown_gate_keys or duplicate_gate_keys or unweighted_gate_keys), + "expected_gate_count": len(expected_gate_set), + "actual_gate_count": len(rendered_gate_keys), + "expected_gate_keys": sorted(expected_gate_set), + "rendered_gate_keys": rendered_gate_keys, + "missing_gate_keys": missing_gate_keys, + "unknown_gate_keys": unknown_gate_keys, + "duplicate_gate_keys": duplicate_gate_keys, + "unweighted_gate_keys": unweighted_gate_keys, + } + + +def min_output_cases(maturity: str) -> int: + if maturity in {"library", "governed"}: + return 5 + if maturity == "production": + return 3 + return 1 + + +def weighted_score(gates: list[dict[str, str]]) -> int: + earned = 0.0 + total = 0.0 + for item in gates: + weight = GATE_WEIGHTS.get(item["key"], 5) + total += weight + if item["status"] == "pass": + earned += weight + elif item["status"] == "warn": + earned += weight * 0.6 + return int(round(earned / total * 100)) if total else 0 diff --git a/scripts/review_studio_gates.py b/scripts/review_studio_gates.py index 9ef8361..2c3d898 100644 --- a/scripts/review_studio_gates.py +++ b/scripts/review_studio_gates.py @@ -3,7 +3,6 @@ import json import os -from collections import Counter from pathlib import Path from typing import Any @@ -14,6 +13,16 @@ except ImportError: # pragma: no cover compute_permission_governance_status = None trust_script_inventory = None +from review_studio_gate_contract import ( + GATE_WEIGHTS, + REVIEW_STUDIO_GATE_KEYS, + add_blockers_from_gate, + gate_contract, + min_output_cases, + status_label, + weighted_score, +) + SCRIPT_INTERFACE = "internal-module" SCRIPT_INTERFACE_REASON = "Imported by render_review_studio.py to keep Review Studio gate evaluation separate from HTML rendering." @@ -21,27 +30,6 @@ SCRIPT_INTERFACE_REASON = "Imported by render_review_studio.py to keep Review St ROOT = Path(__file__).resolve().parent.parent -GATE_WEIGHTS = { - "trigger-lab": 15, - "output-lab": 20, - "context-budget": 10, - "runtime-matrix": 10, - "trust-report": 10, - "python-compat": 10, - "architecture-maintainability": 10, - "permission-gates": 10, - "permission-runtime": 10, - "skill-atlas": 10, - "operations-loop": 10, - "review-waivers": 10, - "world-class-evidence": 10, - "registry-audit": 10, - "release-notes": 10, - "intent-canvas": 10, -} -REVIEW_STUDIO_GATE_KEYS = frozenset(GATE_WEIGHTS) - - def _load_json(path: Path) -> dict[str, Any]: if not path.exists(): return {} @@ -71,37 +59,6 @@ def gate(key: str, label: str, status: str, detail: str, evidence: str, link: st } -def status_label(status: str) -> str: - return {"pass": "通过", "warn": "关注", "block": "阻断"}.get(status, status) - - -def add_blockers_from_gate(gates: list[dict[str, str]]) -> tuple[list[dict[str, str]], list[dict[str, str]]]: - blockers = [item for item in gates if item["status"] == "block"] - warnings = [item for item in gates if item["status"] == "warn"] - return blockers, warnings - - -def gate_contract(gates: list[dict[str, str]]) -> dict[str, Any]: - rendered_gate_keys = [str(item.get("key", "")) for item in gates] - rendered_gate_set = set(rendered_gate_keys) - expected_gate_set = set(REVIEW_STUDIO_GATE_KEYS) - duplicate_gate_keys = sorted(key for key, count in Counter(rendered_gate_keys).items() if count > 1) - missing_gate_keys = sorted(expected_gate_set - rendered_gate_set) - unknown_gate_keys = sorted(rendered_gate_set - expected_gate_set) - unweighted_gate_keys = sorted(rendered_gate_set - set(GATE_WEIGHTS)) - return { - "ok": not (missing_gate_keys or unknown_gate_keys or duplicate_gate_keys or unweighted_gate_keys), - "expected_gate_count": len(expected_gate_set), - "actual_gate_count": len(rendered_gate_keys), - "expected_gate_keys": sorted(expected_gate_set), - "rendered_gate_keys": rendered_gate_keys, - "missing_gate_keys": missing_gate_keys, - "unknown_gate_keys": unknown_gate_keys, - "duplicate_gate_keys": duplicate_gate_keys, - "unweighted_gate_keys": unweighted_gate_keys, - } - - def target_maturity(skill_dir: Path, overview: dict[str, Any]) -> str: manifest = _load_json(skill_dir / "manifest.json") if manifest.get("maturity_tier"): @@ -112,14 +69,6 @@ def target_maturity(skill_dir: Path, overview: dict[str, Any]) -> str: return "scaffold" -def min_output_cases(maturity: str) -> int: - if maturity in {"library", "governed"}: - return 5 - if maturity == "production": - return 3 - return 1 - - def fallback_permission_governance(skill_dir: Path) -> dict[str, Any]: if compute_permission_governance_status is None or trust_script_inventory is None: return {} @@ -692,16 +641,3 @@ def build_gates(skill_dir: Path, output_html: Path, data: dict[str, dict[str, An ) return gates - - -def weighted_score(gates: list[dict[str, str]]) -> int: - earned = 0.0 - total = 0.0 - for item in gates: - weight = GATE_WEIGHTS.get(item["key"], 5) - total += weight - if item["status"] == "pass": - earned += weight - elif item["status"] == "warn": - earned += weight * 0.6 - return int(round(earned / total * 100)) if total else 0 diff --git a/tests/verify_trust_check.py b/tests/verify_trust_check.py index 3e8d099..a9287dd 100644 --- a/tests/verify_trust_check.py +++ b/tests/verify_trust_check.py @@ -99,6 +99,7 @@ def main() -> None: "scripts/compile_skill_targets.py", "scripts/description_optimizer_reporting.py", "scripts/review_studio_formatting.py", + "scripts/review_studio_gate_contract.py", "scripts/review_studio_gates.py", "scripts/review_studio_layout.py", "scripts/trust_check_scripts.py", @@ -134,6 +135,7 @@ def main() -> None: assert "compile_skill_targets.py" not in warning_text, payload["warnings"] assert "description_optimizer_reporting.py" not in warning_text, payload["warnings"] assert "review_studio_formatting.py" not in warning_text, payload["warnings"] + assert "review_studio_gate_contract.py" not in warning_text, payload["warnings"] assert "review_studio_gates.py" not in warning_text, payload["warnings"] assert "review_studio_layout.py" not in warning_text, payload["warnings"] assert "trust_check_scripts.py" not in warning_text, payload["warnings"]