페르소나 소스팩 동기화 정리

This commit is contained in:
Yun Chan 2026-06-28 20:12:35 +09:00
parent 6a81ec596c
commit e8e08935ed
10 changed files with 1126 additions and 283 deletions

View file

@ -0,0 +1,91 @@
"""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())