from __future__ import annotations import hashlib import json from datetime import datetime from pathlib import Path ROOT = Path(__file__).resolve().parents[1] TARGET = ROOT / "app" OUTPUT = ROOT / "docs" / "migration" / "原版资产清单.json" DIRECTORIES = ( "backend", "config", "static", "tests", "tools", "vendor", "游资skills", ) ROOT_FILES = ( ".dockerignore", ".env.example", ".gitignore", "advanced_strategies.py", "alert_service.py", "api_access.py", "app_config.py", "ARCHITECTURE.md", "assistant_agent.py", "chart_data_provider.py", "compose.yaml", "database.py", "demo_data.py", "Dockerfile", "DOCKER_DEPLOY.md", "heaven_agent.py", "heaven_engine.py", "ifind_client.py", "llm_strategy.py", "llm_stream.py", "market_insights.py", "mentor_agent.py", "package-lock.json", "package.json", "playwright.config.js", "README.md", "realtime_aggregator.py", "requirements.txt", "screener.py", "security.py", "sentiment_engine.py", "server.py", "strategy_tracking.py", "sync_data.py", "THIRD_PARTY_NOTICES.md", "trade_journal.py", "tushare_client.py", ) def digest(path: Path) -> str: checksum = hashlib.sha256() with path.open("rb") as handle: for block in iter(lambda: handle.read(1024 * 1024), b""): checksum.update(block) return checksum.hexdigest() def source_files() -> list[Path]: files = [ROOT / name for name in ROOT_FILES] for directory in DIRECTORIES: files.extend( path for path in (ROOT / directory).rglob("*") if path.is_file() and "__pycache__" not in path.parts ) return sorted(set(files), key=lambda path: path.relative_to(ROOT).as_posix()) def build_manifest() -> dict[str, object]: assets = [] mismatches = [] for source in source_files(): relative = source.relative_to(ROOT) target = TARGET / relative source_hash = digest(source) target_hash = digest(target) if target.is_file() else "" status = "identical" if source_hash == target_hash else "mismatch" if status != "identical": mismatches.append(relative.as_posix()) assets.append( { "source": relative.as_posix(), "target": f"app/{relative.as_posix()}", "bytes": source.stat().st_size, "sha256": source_hash, "disposition": "original_copy_pending_move", "status": status, } ) return { "schema_version": 1, "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"), "source_commit": "41329943c4878fc09ed82ec376eb93ab151e4092", "source_root": ".", "target_root": "app", "excluded": [ "next/", "data/review.db and SQLite sidecars", "data/private-mentor-skills/", ".env", "node_modules/", "logs, caches and generated test results", ], "asset_count": len(assets), "mismatch_count": len(mismatches), "mismatches": mismatches, "assets": assets, } def main() -> int: manifest = build_manifest() OUTPUT.write_text( json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) print( f"assets={manifest['asset_count']} mismatches={manifest['mismatch_count']} " f"output={OUTPUT}" ) return 1 if manifest["mismatch_count"] else 0 if __name__ == "__main__": raise SystemExit(main())