120 lines
4.6 KiB
Python
120 lines
4.6 KiB
Python
#!/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"}
|
|
GENERATED_DOCS = {
|
|
"INSTALL.md": """# Jyotish Skill Install
|
|
|
|
1. Unzip this package into a local folder.
|
|
2. Run `python3 scripts/public_release_privacy_scan.py`.
|
|
3. Run `python3 scripts/user_invocation_acceptance_check.py`.
|
|
4. If the check passes, load this folder as a Codex skill/plugin or call `mcp_server.py`.
|
|
5. VedAstro cloud evidence is optional. Without `official_raw_response`, reports must say `official_blocked` or `local_fallback`.
|
|
|
|
Do not add private birth data, API keys, or desktop oracle screenshots to this package.
|
|
""",
|
|
"USER_PROMPTS.md": """# User Prompts
|
|
|
|
## User Has No Question
|
|
|
|
请根据我的出生信息先运行统一主链,生成 evidence_packet、guided_topics 和 Technique Audit Table。
|
|
不要反问我想看什么;请先给出 3-5 个最值得继续看的方向,并附可直接复制的问题。
|
|
|
|
## High-Rigor Reading
|
|
|
|
请使用 strict_workflow,并在输出中标明 VedAstro / PyJHora-JHora / jyotishganit / Real Case Calibration 的状态。
|
|
如果没有 VedAstro official_raw_response,请标记 official_blocked 或 local_fallback。
|
|
|
|
## Birth-Time Rectification
|
|
|
|
请使用主动问询式校时:先生成候选时间扫描和选择题,我只回答 A/B/C/D。
|
|
""",
|
|
}
|
|
|
|
|
|
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,
|
|
"generated_files": sorted(GENERATED_DOCS),
|
|
"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)
|
|
for rel, text in GENERATED_DOCS.items():
|
|
archive.writestr(rel, text)
|
|
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())
|