CC: prepare generic VISION package candidate
This commit is contained in:
@@ -3,20 +3,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REQUIRED = ["AGENTS.md", "README.md", "WORKBOARD.yaml"]
|
||||
REQUIRED = [
|
||||
"AGENTS.md",
|
||||
"README.md",
|
||||
"WORKBOARD.yaml",
|
||||
"CONTEXT.md",
|
||||
"docs/VISION-PACKAGE-CANDIDATE.md",
|
||||
"docs/VISUAL-EVIDENCE-CONTRACT.md",
|
||||
"docs/VISION-HOST-ADAPTER-CANDIDATES.md",
|
||||
"candidate-manifests/vision-package-candidate.json",
|
||||
"candidate-manifests/vision-tool-grants.json",
|
||||
"candidate-manifests/visual-evidence-contract.json",
|
||||
]
|
||||
VALIDATORS = [
|
||||
"tools/validate_vision_package_candidate.py",
|
||||
"tools/validate_vision_tool_grants.py",
|
||||
"tools/validate_visual_evidence_contract.py",
|
||||
"tools/validate_vision_host_adapter_candidates.py",
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
errors: list[str] = []
|
||||
validator_outputs: dict[str, str] = {}
|
||||
for rel in REQUIRED:
|
||||
if not (ROOT / rel).exists():
|
||||
errors.append(f"missing:{rel}")
|
||||
checks = {
|
||||
"AGENTS.md": ["child-local", "not Cortex OS Core authority", "python3 tools/validate_svrnty_vision_child.py"],
|
||||
"WORKBOARD.yaml": ["SVRNTY-VISION-WORK-001", "status: candidate", "owner: jp"],
|
||||
"CONTEXT.md": ["Visual Perception Package Candidate", "Research Handoff"],
|
||||
"docs/VISION-PACKAGE-CANDIDATE.md": ["Research reads sources; Vision sees media", "wildcard grant"],
|
||||
}
|
||||
for rel, snippets in checks.items():
|
||||
path = ROOT / rel
|
||||
@@ -25,7 +47,29 @@ def main() -> int:
|
||||
for snippet in snippets:
|
||||
if snippet not in text:
|
||||
errors.append(f"{rel}:missing:{snippet}")
|
||||
result = {"ok": not errors, "validator": "svrnty-vision-child-v1", "checked": REQUIRED, "errors": errors, "warnings": []}
|
||||
for rel in VALIDATORS:
|
||||
path = ROOT / rel
|
||||
if not path.exists():
|
||||
errors.append(f"missing:{rel}")
|
||||
continue
|
||||
completed = subprocess.run(
|
||||
[sys.executable, str(path)],
|
||||
cwd=ROOT,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
validator_outputs[rel] = completed.stdout.strip()
|
||||
if completed.returncode != 0:
|
||||
errors.append(f"validator_failed:{rel}:{completed.stderr.strip()}")
|
||||
result = {
|
||||
"ok": not errors,
|
||||
"validator": "svrnty-vision-child-v1",
|
||||
"checked": REQUIRED + VALIDATORS,
|
||||
"errors": errors,
|
||||
"warnings": [],
|
||||
"validator_outputs": validator_outputs,
|
||||
}
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
return 0 if result["ok"] else 1
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate VISION host adapter candidate parity."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
HOSTS = {
|
||||
"claude-code": "candidate-manifests/host-adapters/vision-claude-code-adapter.json",
|
||||
"codex-cli": "candidate-manifests/host-adapters/vision-codex-cli-adapter.json",
|
||||
"pi-code": "candidate-manifests/host-adapters/vision-pi-code-adapter.json",
|
||||
}
|
||||
|
||||
CURRENT_TOOLS = [
|
||||
"vision.image_analyze",
|
||||
"vision.image_generate",
|
||||
"vision.palette_extract",
|
||||
"vision.background_cutout",
|
||||
]
|
||||
|
||||
PLANNED_TOOLS = [
|
||||
"vision.ocr_read",
|
||||
"vision.screenshot_observe",
|
||||
"vision.browser_observe",
|
||||
"vision.document_layout_read",
|
||||
"vision.chart_read",
|
||||
"vision.table_read",
|
||||
"vision.diagram_read",
|
||||
"vision.object_detect",
|
||||
"vision.visual_ground",
|
||||
"vision.segment",
|
||||
"vision.video_read",
|
||||
"vision.image_edit",
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
errors: list[str] = []
|
||||
checked = list(HOSTS.values()) + ["docs/VISION-HOST-ADAPTER-CANDIDATES.md"]
|
||||
manifests: dict[str, dict] = {}
|
||||
for host, rel in HOSTS.items():
|
||||
manifest = _read_json(ROOT / rel, errors)
|
||||
if manifest:
|
||||
manifests[host] = manifest
|
||||
|
||||
for host, manifest in manifests.items():
|
||||
_expect(manifest.get("host_runtime") == host, f"host_runtime_mismatch:{host}", errors)
|
||||
_expect(manifest.get("package_id") == "visual-perception-package-candidate", f"package_mismatch:{host}", errors)
|
||||
_expect(manifest.get("adapter_role") == "thin_access_adapter", f"adapter_role_mismatch:{host}", errors)
|
||||
_expect(manifest.get("wildcard_grant_allowed") is False, f"wildcard_grant_allowed:{host}", errors)
|
||||
_expect(manifest.get("current_tool_candidates") == CURRENT_TOOLS, f"current_tools_mismatch:{host}", errors)
|
||||
_expect(manifest.get("planned_tool_candidates") == PLANNED_TOOLS, f"planned_tools_mismatch:{host}", errors)
|
||||
disclosures = manifest.get("disclosure_contract", {})
|
||||
for key in ("provider_mode_required", "retention_required", "visual_evidence_required"):
|
||||
if disclosures.get(key) is not True:
|
||||
errors.append(f"disclosure_missing:{host}:{key}")
|
||||
forbidden = set(manifest.get("forbidden_effects", []))
|
||||
for effect in ("research_synthesis", "textual_web_search", "runtime_start", "wildcard_tool_exposure"):
|
||||
if effect not in forbidden:
|
||||
errors.append(f"forbidden_effect_missing:{host}:{effect}")
|
||||
for key, value in manifest.get("non_authorization", {}).items():
|
||||
if value is not False:
|
||||
errors.append(f"non_authorization_enabled:{host}:{key}")
|
||||
|
||||
if len(manifests) == len(HOSTS):
|
||||
current_sets = {tuple(m["current_tool_candidates"]) for m in manifests.values()}
|
||||
planned_sets = {tuple(m["planned_tool_candidates"]) for m in manifests.values()}
|
||||
_expect(len(current_sets) == 1, "current_tool_parity_failed", errors)
|
||||
_expect(len(planned_sets) == 1, "planned_tool_parity_failed", errors)
|
||||
|
||||
_check_snippets(
|
||||
"docs/VISION-HOST-ADAPTER-CANDIDATES.md",
|
||||
["Claude Code", "Codex CLI", "Pi-Code", "no wildcard grant"],
|
||||
errors,
|
||||
)
|
||||
|
||||
result = {
|
||||
"ok": not errors,
|
||||
"validator": "vision-host-adapter-candidates-v1",
|
||||
"checked": checked,
|
||||
"errors": errors,
|
||||
"warnings": [],
|
||||
}
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
return 0 if result["ok"] else 1
|
||||
|
||||
|
||||
def _read_json(path: Path, errors: list[str]) -> dict | None:
|
||||
if not path.exists():
|
||||
errors.append(f"missing:{path.relative_to(ROOT)}")
|
||||
return None
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
errors.append(f"{path.relative_to(ROOT)}:json:{exc}")
|
||||
return None
|
||||
|
||||
|
||||
def _check_snippets(rel: str, snippets: list[str], errors: list[str]) -> None:
|
||||
path = ROOT / rel
|
||||
if not path.exists():
|
||||
errors.append(f"missing:{rel}")
|
||||
return
|
||||
text = path.read_text(encoding="utf-8")
|
||||
for snippet in snippets:
|
||||
if snippet not in text:
|
||||
errors.append(f"{rel}:missing:{snippet}")
|
||||
|
||||
|
||||
def _expect(condition: bool, error: str, errors: list[str]) -> None:
|
||||
if not condition:
|
||||
errors.append(error)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the child-local VISION package candidate manifest and docs."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
CURRENT_TOOLS = [
|
||||
"vision.image_analyze",
|
||||
"vision.image_generate",
|
||||
"vision.palette_extract",
|
||||
"vision.background_cutout",
|
||||
]
|
||||
|
||||
PLANNED_TOOLS = [
|
||||
"vision.ocr_read",
|
||||
"vision.screenshot_observe",
|
||||
"vision.browser_observe",
|
||||
"vision.document_layout_read",
|
||||
"vision.chart_read",
|
||||
"vision.table_read",
|
||||
"vision.diagram_read",
|
||||
"vision.object_detect",
|
||||
"vision.visual_ground",
|
||||
"vision.segment",
|
||||
"vision.video_read",
|
||||
"vision.image_edit",
|
||||
]
|
||||
|
||||
ROUTE_TOOLS = {
|
||||
"POST /vlm/analyze": "vision.image_analyze",
|
||||
"POST /flux/render": "vision.image_generate",
|
||||
"POST /palette/extract": "vision.palette_extract",
|
||||
"POST /rembg/cutout": "vision.background_cutout",
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
errors: list[str] = []
|
||||
required = [
|
||||
"CONTEXT.md",
|
||||
"docs/VISION-PACKAGE-CANDIDATE.md",
|
||||
"candidate-manifests/vision-package-candidate.json",
|
||||
]
|
||||
for rel in required:
|
||||
if not (ROOT / rel).exists():
|
||||
errors.append(f"missing:{rel}")
|
||||
|
||||
manifest_path = ROOT / "candidate-manifests/vision-package-candidate.json"
|
||||
manifest = _read_json(manifest_path, errors)
|
||||
if manifest:
|
||||
_expect(manifest.get("canonical_sense") == "VISION", "canonical_sense_not_vision", errors)
|
||||
_expect(
|
||||
manifest.get("status") == "candidate_only_not_seed_installed",
|
||||
"status_not_candidate_only",
|
||||
errors,
|
||||
)
|
||||
_expect(manifest.get("current_tool_candidates") == CURRENT_TOOLS, "current_tools_mismatch", errors)
|
||||
_expect(manifest.get("planned_tool_candidates") == PLANNED_TOOLS, "planned_tools_mismatch", errors)
|
||||
route_map = {
|
||||
item.get("route"): item.get("tool_id")
|
||||
for item in manifest.get("current_route_adapters", [])
|
||||
}
|
||||
_expect(route_map == ROUTE_TOOLS, "route_tool_map_mismatch", errors)
|
||||
grant_policy = manifest.get("grant_policy", {})
|
||||
_expect(grant_policy.get("wildcard_grant_allowed") is False, "wildcard_grant_allowed", errors)
|
||||
_expect(
|
||||
grant_policy.get("package_default_grants_all_tools") is False,
|
||||
"package_default_grants_all_tools",
|
||||
errors,
|
||||
)
|
||||
_expect(grant_policy.get("tool_grants_are_granular") is True, "tool_grants_not_granular", errors)
|
||||
for key, value in manifest.get("non_authorization", {}).items():
|
||||
if value is not False:
|
||||
errors.append(f"non_authorization_enabled:{key}")
|
||||
forbidden = set(manifest.get("forbidden_research_owned_capabilities", []))
|
||||
for capability in ("web_search", "fetch_page", "extract_pdf", "deep_research", "research_synthesis"):
|
||||
if capability not in forbidden:
|
||||
errors.append(f"missing_forbidden_research_capability:{capability}")
|
||||
|
||||
_check_snippets(
|
||||
"docs/VISION-PACKAGE-CANDIDATE.md",
|
||||
[
|
||||
"Research reads sources; Vision sees media",
|
||||
"No Core promotion",
|
||||
"wildcard grant",
|
||||
"`vision.image_analyze`",
|
||||
"`vision.video_read`",
|
||||
],
|
||||
errors,
|
||||
)
|
||||
_check_snippets(
|
||||
"CONTEXT.md",
|
||||
["Visual Evidence", "Research Handoff", "BTE Compatibility Surface"],
|
||||
errors,
|
||||
)
|
||||
|
||||
result = {
|
||||
"ok": not errors,
|
||||
"validator": "vision-package-candidate-v1",
|
||||
"checked": required,
|
||||
"errors": errors,
|
||||
"warnings": [],
|
||||
}
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
return 0 if result["ok"] else 1
|
||||
|
||||
|
||||
def _read_json(path: Path, errors: list[str]) -> dict | None:
|
||||
if not path.exists():
|
||||
return None
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
errors.append(f"{path.relative_to(ROOT)}:json:{exc}")
|
||||
return None
|
||||
|
||||
|
||||
def _check_snippets(rel: str, snippets: list[str], errors: list[str]) -> None:
|
||||
path = ROOT / rel
|
||||
if not path.exists():
|
||||
return
|
||||
text = path.read_text(encoding="utf-8")
|
||||
for snippet in snippets:
|
||||
if snippet not in text:
|
||||
errors.append(f"{rel}:missing:{snippet}")
|
||||
|
||||
|
||||
def _expect(condition: bool, error: str, errors: list[str]) -> None:
|
||||
if not condition:
|
||||
errors.append(error)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate granular VISION tool grant candidate manifest."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
CURRENT_TOOLS = [
|
||||
"vision.image_analyze",
|
||||
"vision.image_generate",
|
||||
"vision.palette_extract",
|
||||
"vision.background_cutout",
|
||||
]
|
||||
|
||||
PLANNED_TOOLS = [
|
||||
"vision.ocr_read",
|
||||
"vision.screenshot_observe",
|
||||
"vision.browser_observe",
|
||||
"vision.document_layout_read",
|
||||
"vision.chart_read",
|
||||
"vision.table_read",
|
||||
"vision.diagram_read",
|
||||
"vision.object_detect",
|
||||
"vision.visual_ground",
|
||||
"vision.segment",
|
||||
"vision.video_read",
|
||||
"vision.image_edit",
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
errors: list[str] = []
|
||||
path = ROOT / "candidate-manifests/vision-tool-grants.json"
|
||||
manifest = _read_json(path, errors)
|
||||
if manifest:
|
||||
_expect(manifest.get("canonical_sense") == "VISION", "canonical_sense_not_vision", errors)
|
||||
_expect(manifest.get("wildcard_grant_allowed") is False, "wildcard_grant_allowed", errors)
|
||||
_expect(
|
||||
manifest.get("package_default_grants_all_tools") is False,
|
||||
"package_default_grants_all_tools",
|
||||
errors,
|
||||
)
|
||||
current = manifest.get("current_tool_candidates", [])
|
||||
current_tool_ids = [item.get("tool_id") for item in current]
|
||||
_expect(current_tool_ids == CURRENT_TOOLS, "current_tool_ids_mismatch", errors)
|
||||
_expect(manifest.get("planned_tool_candidates") == PLANNED_TOOLS, "planned_tools_mismatch", errors)
|
||||
for item in current:
|
||||
if item.get("grant_default") is not False:
|
||||
errors.append(f"grant_default_enabled:{item.get('tool_id')}")
|
||||
disclosures = manifest.get("required_disclosures", {})
|
||||
for field in (
|
||||
"producing_package_id",
|
||||
"producing_tool_id",
|
||||
"capability_surface",
|
||||
"provider_mode",
|
||||
"retention_disclosure",
|
||||
"validation_status",
|
||||
):
|
||||
if disclosures.get(field) is not True:
|
||||
errors.append(f"required_disclosure_missing:{field}")
|
||||
forbidden = set(manifest.get("forbidden_effects", []))
|
||||
for effect in (
|
||||
"research_synthesis",
|
||||
"textual_web_search",
|
||||
"textual_page_fetch",
|
||||
"deep_research_workflow",
|
||||
"wildcard_tool_exposure",
|
||||
):
|
||||
if effect not in forbidden:
|
||||
errors.append(f"forbidden_effect_missing:{effect}")
|
||||
|
||||
result = {
|
||||
"ok": not errors,
|
||||
"validator": "vision-tool-grants-v1",
|
||||
"checked": ["candidate-manifests/vision-tool-grants.json"],
|
||||
"errors": errors,
|
||||
"warnings": [],
|
||||
}
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
return 0 if result["ok"] else 1
|
||||
|
||||
|
||||
def _read_json(path: Path, errors: list[str]) -> dict | None:
|
||||
if not path.exists():
|
||||
errors.append(f"missing:{path.relative_to(ROOT)}")
|
||||
return None
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
errors.append(f"{path.relative_to(ROOT)}:json:{exc}")
|
||||
return None
|
||||
|
||||
|
||||
def _expect(condition: bool, error: str, errors: list[str]) -> None:
|
||||
if not condition:
|
||||
errors.append(error)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the Visual Evidence contract and pure adapter proof."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
REQUIRED_FIELDS = [
|
||||
"producing_package_id",
|
||||
"producing_tool_id",
|
||||
"capability_surface",
|
||||
"source_reference",
|
||||
"provider_mode",
|
||||
"retention_disclosure",
|
||||
"observed_content_summary",
|
||||
"extracted_claims",
|
||||
"confidence",
|
||||
"caveats",
|
||||
"timestamp",
|
||||
"validation_status",
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
errors: list[str] = []
|
||||
required = [
|
||||
"docs/VISUAL-EVIDENCE-CONTRACT.md",
|
||||
"candidate-manifests/visual-evidence-contract.json",
|
||||
"src/svrnty_vision/visual_evidence.py",
|
||||
"tests/test_visual_evidence.py",
|
||||
]
|
||||
for rel in required:
|
||||
if not (ROOT / rel).exists():
|
||||
errors.append(f"missing:{rel}")
|
||||
|
||||
manifest = _read_json(ROOT / "candidate-manifests/visual-evidence-contract.json", errors)
|
||||
if manifest:
|
||||
_expect(manifest.get("required_fields") == REQUIRED_FIELDS, "required_fields_mismatch", errors)
|
||||
proof = manifest.get("first_vertical_proof", {})
|
||||
_expect(proof.get("source_tool_id") == "vision.image_analyze", "proof_tool_mismatch", errors)
|
||||
_expect(proof.get("live_provider_call_required") is False, "live_provider_required", errors)
|
||||
handoff = manifest.get("research_handoff", {})
|
||||
_expect(handoff.get("research_may_consume_visual_evidence") is True, "research_handoff_missing", errors)
|
||||
_expect(handoff.get("vision_may_write_research_capsules") is False, "vision_writes_capsules", errors)
|
||||
_expect(handoff.get("vision_may_perform_research_synthesis") is False, "vision_research_synthesis", errors)
|
||||
|
||||
_check_snippets(
|
||||
"docs/VISUAL-EVIDENCE-CONTRACT.md",
|
||||
[
|
||||
"`producing_package_id`",
|
||||
"Research owns synthesis and capsule writing",
|
||||
"does not call a live provider",
|
||||
],
|
||||
errors,
|
||||
)
|
||||
_validate_adapter(errors)
|
||||
|
||||
result = {
|
||||
"ok": not errors,
|
||||
"validator": "visual-evidence-contract-v1",
|
||||
"checked": required,
|
||||
"errors": errors,
|
||||
"warnings": [],
|
||||
}
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
return 0 if result["ok"] else 1
|
||||
|
||||
|
||||
def _validate_adapter(errors: list[str]) -> None:
|
||||
try:
|
||||
from types import SimpleNamespace
|
||||
|
||||
from svrnty_vision.visual_evidence import visual_evidence_from_vlm_response
|
||||
except Exception as exc: # pragma: no cover - validator import report
|
||||
errors.append(f"adapter_import_failed:{type(exc).__name__}:{exc}")
|
||||
return
|
||||
|
||||
response = SimpleNamespace(
|
||||
rubric_mode="raw",
|
||||
justification="",
|
||||
model_id="qwen-test",
|
||||
raw_scores_json='{"description":"solid red square","objects":["red square"],"detected_text":[]}',
|
||||
)
|
||||
evidence = visual_evidence_from_vlm_response(
|
||||
response,
|
||||
source_reference="fixture://red-square.png",
|
||||
provider_mode="sovereign",
|
||||
retention_disclosure="synchronous_no_async_persistence",
|
||||
timestamp="2026-06-06T00:00:00Z",
|
||||
)
|
||||
dumped = evidence.model_dump()
|
||||
for field in REQUIRED_FIELDS:
|
||||
if field not in dumped:
|
||||
errors.append(f"adapter_missing_field:{field}")
|
||||
_expect(dumped.get("producing_package_id") == "visual-perception-package-candidate", "adapter_package_mismatch", errors)
|
||||
_expect(dumped.get("producing_tool_id") == "vision.image_analyze", "adapter_tool_mismatch", errors)
|
||||
_expect(dumped.get("observed_content_summary") == "solid red square", "adapter_summary_mismatch", errors)
|
||||
_expect("description: solid red square" in dumped.get("extracted_claims", []), "adapter_claim_missing", errors)
|
||||
|
||||
|
||||
def _read_json(path: Path, errors: list[str]) -> dict | None:
|
||||
if not path.exists():
|
||||
return None
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
errors.append(f"{path.relative_to(ROOT)}:json:{exc}")
|
||||
return None
|
||||
|
||||
|
||||
def _check_snippets(rel: str, snippets: list[str], errors: list[str]) -> None:
|
||||
path = ROOT / rel
|
||||
if not path.exists():
|
||||
return
|
||||
text = path.read_text(encoding="utf-8")
|
||||
for snippet in snippets:
|
||||
if snippet not in text:
|
||||
errors.append(f"{rel}:missing:{snippet}")
|
||||
|
||||
|
||||
def _expect(condition: bool, error: str, errors: list[str]) -> None:
|
||||
if not condition:
|
||||
errors.append(error)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user