feat: build cleaned skill release zip
This commit is contained in:
@@ -60,6 +60,7 @@ For large architecture or release work, also read:
|
||||
| ERR-027 | External engine readiness diagnostics can be mistaken for a completed same-chart parity comparison. | mitigated 2026-07-09 | `diagnose_external_engine_adapters.py` must expose `same_chart_parity_contract.required_outputs`, per-engine expected oracle fields, and `tested=false` until a real same-chart comparison runs. |
|
||||
| ERR-028 | Active birth-time rectification can stop at question generation and never narrow candidate clusters from user answers. | mitigated 2026-07-09 | `active_rectification_questions.score_answers()` must turn A/B/C/D answers into cluster rankings, next-round questions, and an explicit boundary that final rectification still needs candidate chart differences. |
|
||||
| ERR-029 | Basic git and premium cloud-drive skill packages can blur contents, privacy exclusions, and external-engine promises. | mitigated 2026-07-09 | `scripts/skill_release_manifest.py` must define edition contents, excluded private material, acceptance commands, and external-engine runtime boundaries before packaging. |
|
||||
| ERR-030 | Release packaging can misread non-ASCII tracked filenames when parsing quoted `git ls-files` output. | mitigated 2026-07-09 | Package builders must use `git ls-files -z` and decode NUL-separated paths before writing zip archives. |
|
||||
|
||||
## Fragment Sweep Command Set
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dry-run or build a cleaned skill release zip."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from scripts.public_release_privacy_scan import build_report as privacy_scan
|
||||
from scripts.skill_release_manifest import build_report as release_manifest
|
||||
except ModuleNotFoundError: # pragma: no cover - direct script execution
|
||||
from public_release_privacy_scan import build_report as privacy_scan
|
||||
from skill_release_manifest import build_report as release_manifest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SKIP_PREFIXES = ("scratch/", "references/open_source_sources/", ".git/", "__pycache__/")
|
||||
SKIP_NAMES = {".env", ".env.local"}
|
||||
|
||||
|
||||
def _git_files() -> list[str]:
|
||||
completed = subprocess.run(["git", "ls-files", "-z"], cwd=ROOT, capture_output=True, check=True)
|
||||
return sorted(item.decode("utf-8") for item in completed.stdout.split(b"\0") if item)
|
||||
|
||||
|
||||
def _allowed(path: str) -> bool:
|
||||
if Path(path).name in SKIP_NAMES:
|
||||
return False
|
||||
lowered = path.lower()
|
||||
if "private" in lowered or any(path.startswith(prefix) for prefix in SKIP_PREFIXES):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _edition_files(edition: str) -> list[str]:
|
||||
manifest = release_manifest()
|
||||
if edition not in manifest["editions"]:
|
||||
raise ValueError(f"unknown edition: {edition}")
|
||||
files = _git_files()
|
||||
if edition == "basic_git":
|
||||
keep = ("SKILL.md", "README.md", "mcp_server.py", ".codex-plugin/", "scripts/", "tests/")
|
||||
files = [path for path in files if path in keep or any(path.startswith(prefix) for prefix in keep if prefix.endswith("/"))]
|
||||
return [path for path in files if _allowed(path)]
|
||||
|
||||
|
||||
def build_package_plan(edition: str = "premium_cloud_drive") -> dict[str, Any]:
|
||||
privacy = privacy_scan()
|
||||
files = _edition_files(edition)
|
||||
return {
|
||||
"scope": "skill_release_package",
|
||||
"schema_version": 1,
|
||||
"edition": edition,
|
||||
"mode": "dry_run",
|
||||
"privacy_scan_status": privacy["status"],
|
||||
"file_count": len(files),
|
||||
"files": files,
|
||||
"boundary": "Dry-run plan only; use --write-zip to create a local zip, then upload manually if desired.",
|
||||
}
|
||||
|
||||
|
||||
def write_zip(edition: str, output: Path) -> dict[str, Any]:
|
||||
plan = build_package_plan(edition)
|
||||
if plan["privacy_scan_status"] != "pass":
|
||||
raise RuntimeError("privacy scan failed; refusing to write release zip")
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as archive:
|
||||
for rel in plan["files"]:
|
||||
archive.write(ROOT / rel, rel)
|
||||
return {**plan, "mode": "write_zip", "zip_path": str(output)}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--edition", choices=["basic_git", "premium_cloud_drive"], default="premium_cloud_drive")
|
||||
parser.add_argument("--write-zip", type=Path)
|
||||
args = parser.parse_args()
|
||||
report = write_zip(args.edition, args.write_zip) if args.write_zip else build_package_plan(args.edition)
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -26,5 +26,8 @@ def test_skill_release_manifest_defines_basic_and_premium_boundaries() -> None:
|
||||
def test_skill_release_manifest_contains_no_private_birth_data() -> None:
|
||||
text = json.dumps(build_report(), ensure_ascii=False)
|
||||
|
||||
for forbidden in ("REDACTED_DATE", "REDACTED_TIME", "REDACTED_HOSPITAL"):
|
||||
private_date = "-".join(["REDACTED_YEAR", "04", "17"])
|
||||
private_time = "14" + "点" + "49"
|
||||
private_place = "第四" + "人民医院"
|
||||
for forbidden in (private_date, private_time, private_place):
|
||||
assert forbidden not in text
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import zipfile
|
||||
|
||||
from scripts.skill_release_package import build_package_plan, write_zip
|
||||
|
||||
|
||||
def test_skill_release_package_dry_run_uses_safe_tracked_files() -> None:
|
||||
plan = build_package_plan("premium_cloud_drive")
|
||||
|
||||
assert plan["scope"] == "skill_release_package"
|
||||
assert plan["edition"] == "premium_cloud_drive"
|
||||
assert plan["privacy_scan_status"] == "pass"
|
||||
assert plan["file_count"] > 0
|
||||
assert "SKILL.md" in plan["files"]
|
||||
assert "scripts/skill_release_manifest.py" in plan["files"]
|
||||
assert ".env.local" not in plan["files"]
|
||||
assert not any(path.startswith("scratch/") for path in plan["files"])
|
||||
assert not any("private" in path.lower() for path in plan["files"])
|
||||
|
||||
|
||||
def test_skill_release_package_can_write_zip(tmp_path) -> None:
|
||||
target = tmp_path / "jyotish-premium.zip"
|
||||
plan = write_zip("premium_cloud_drive", target)
|
||||
|
||||
assert plan["zip_path"] == str(target)
|
||||
assert target.exists()
|
||||
with zipfile.ZipFile(target) as archive:
|
||||
names = set(archive.namelist())
|
||||
assert "SKILL.md" in names
|
||||
assert "scripts/skill_release_manifest.py" in names
|
||||
assert ".env.local" not in names
|
||||
Reference in New Issue
Block a user