91 lines
3.2 KiB
Python
91 lines
3.2 KiB
Python
"""Sync repo-managed persona/evaluator source packs into KB.
|
|
|
|
Default mode is a DB-backed dry run: compare repo source pack content hashes
|
|
against active kb.document rows and report the versions that would be used.
|
|
Use --apply to upsert kb.source rows and index changed documents.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import dataclasses
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
API_ROOT = REPO_ROOT / "apps" / "api"
|
|
if str(API_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(API_ROOT))
|
|
|
|
|
|
from app.db import acquire, close_pool, init_pool # noqa: E402
|
|
from app.services import source_pack_sync # noqa: E402
|
|
|
|
|
|
def _to_payload(result: source_pack_sync.RepoSourcePackSyncResult) -> dict[str, Any]:
|
|
return {
|
|
"applied": result.applied,
|
|
"sources_upserted": result.sources_upserted,
|
|
"manifest_count": result.manifest_count,
|
|
"chunks_indexed": result.chunks_indexed,
|
|
"skipped_unchanged": result.skipped_unchanged,
|
|
"embedded": result.embedded,
|
|
"degraded": result.degraded,
|
|
"items": [dataclasses.asdict(item) for item in result.items],
|
|
}
|
|
|
|
|
|
def _print_text(result: source_pack_sync.RepoSourcePackSyncResult) -> None:
|
|
mode = "APPLY" if result.applied else "DRY-RUN"
|
|
print(f"persona source pack sync: {mode}")
|
|
print(f"manifest_count: {result.manifest_count}")
|
|
print(f"sources_upserted: {result.sources_upserted}")
|
|
print(f"chunks_indexed: {result.chunks_indexed}")
|
|
print(f"skipped_unchanged: {result.skipped_unchanged}")
|
|
for item in result.items:
|
|
previous = "-" if item.previous_version is None else str(item.previous_version)
|
|
action = "skip" if item.skipped_unchanged else ("apply" if result.applied else "would_apply")
|
|
print(
|
|
f"- {item.source_id} {action} v{previous}->v{item.new_version} "
|
|
f"{item.content_hash[:12]} chunks={item.chunks_indexed}"
|
|
)
|
|
|
|
|
|
async def _main_async(args: argparse.Namespace) -> int:
|
|
await init_pool()
|
|
try:
|
|
async with acquire(role="admin") as conn:
|
|
result = await source_pack_sync.sync_repo_source_packs(conn, apply=bool(args.apply))
|
|
finally:
|
|
await close_pool()
|
|
if args.json:
|
|
print(json.dumps(_to_payload(result), ensure_ascii=False, indent=2, sort_keys=True))
|
|
else:
|
|
_print_text(result)
|
|
return 0
|
|
|
|
|
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description=(
|
|
"Compare or apply repo-managed persona/evaluator source packs. "
|
|
"Dry-run is the default and reads DB state without writes; --apply "
|
|
"upserts source rows and indexes changed document versions."
|
|
)
|
|
)
|
|
parser.add_argument("--apply", action="store_true", help="write changed source packs into KB")
|
|
parser.add_argument("--json", action="store_true", help="emit machine-readable JSON")
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = parse_args(list(argv if argv is not None else sys.argv[1:]))
|
|
return asyncio.run(_main_async(args))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|