from __future__ import annotations import json from pathlib import Path from typing import Any from .config import settings from .models import StandardAnswer, StandardKind from .skillflow import make_standard_id, tags_for_text, load_standard_from_file class KnowledgeRepository: def __init__(self) -> None: self._standards: dict[str, StandardAnswer] = {} self.reload() def reload(self) -> None: standards: dict[str, StandardAnswer] = {} for item in self._load_manifest_standards(): standards[item.standard_id] = item for item in self._load_framework_refs(): standards.setdefault(item.standard_id, item) for item in self._load_local_json_standards(): standards.setdefault(item.standard_id, item) self._standards = standards def list_standards(self, query: str = "", kind: StandardKind | None = None) -> list[StandardAnswer]: items = list(self._standards.values()) if kind and kind != StandardKind.unknown: items = [item for item in items if item.kind == kind] if query.strip(): tokens = _tokens(query) scored = [(score_standard(tokens, item), item) for item in items] items = [item for score, item in sorted(scored, key=lambda pair: pair[0], reverse=True) if score > 0] return items def require(self, standard_id: str) -> StandardAnswer: if standard_id not in self._standards: raise KeyError(f"standard answer not found: {standard_id}") return self._standards[standard_id] def _load_local_json_standards(self) -> list[StandardAnswer]: result: list[StandardAnswer] = [] for root in settings.standard_skillflow_dirs: if not root.exists(): continue for path in root.rglob("*.json"): if not _looks_like_skillflow_name(path.name): continue standard = load_standard_from_file(path) if standard: result.append(standard) return result def _load_manifest_standards(self) -> list[StandardAnswer]: manifest_path = settings.standard_skillflow_dirs[0] / "manifest.json" if not manifest_path.exists(): return [] try: manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig")) except Exception: return [] entries = manifest.get("entries") if not isinstance(entries, list): return [] result: list[StandardAnswer] = [] root = manifest_path.parent for entry in entries: if not isinstance(entry, dict): continue standard = self._standard_from_manifest_entry(root, entry) if standard: result.append(standard) return result def _standard_from_manifest_entry(self, root: Path, entry: dict[str, Any]) -> StandardAnswer | None: preferred = ( entry.get("modeling_plan_path") or entry.get("knowledge_skillflow_path") or entry.get("skill_flow_path") or "" ) payload_path = root / str(preferred) loaded = load_standard_from_file(payload_path) if preferred else None if loaded is None: return StandardAnswer( standard_id=str(entry.get("standard_id") or ""), title=str(entry.get("title") or entry.get("source_base_name") or ""), kind=_standard_kind(str(entry.get("kind") or "unknown")), source_path=str(payload_path) if preferred else "", source_type="manifest_ref", tags=tags_for_text(f"{entry.get('title', '')} {entry.get('category', '')}"), summary="Manifest standard answer reference without a readable local payload.", steps=[], raw={"manifest_entry": entry}, ) loaded.standard_id = str(entry.get("standard_id") or loaded.standard_id) loaded.title = str(entry.get("title") or loaded.title) loaded.kind = _standard_kind(str(entry.get("kind") or loaded.kind.value)) loaded.source_type = "manifest" loaded.tags = sorted(set(loaded.tags + tags_for_text(f"{loaded.title} {entry.get('category', '')}"))) loaded.summary = loaded.summary or f"Manifest standard answer: {loaded.title}" loaded.raw = { "manifest_entry": entry, "payload": loaded.raw, } return loaded def _load_framework_refs(self) -> list[StandardAnswer]: path = settings.knowledge_framework_path if settings.knowledge_framework_path.exists() else settings.legacy_knowledge_framework_path if not path.exists(): return [] try: framework = json.loads(path.read_text(encoding="utf-8-sig")) except Exception: return [] result: list[StandardAnswer] = [] impl = framework.get("function_implementation_library", {}) if isinstance(framework, dict) else {} for section in impl.get("sections", []) if isinstance(impl.get("sections"), list) else []: for flow in section.get("skillflows", []) if isinstance(section, dict) and isinstance(section.get("skillflows"), list) else []: result.append(_standard_ref(flow, "assembly", path)) for part in framework.get("part_library", []) if isinstance(framework, dict) and isinstance(framework.get("part_library"), list) else []: category = str(part.get("category_name") or part.get("category_code") or "") for flow in part.get("modeling_skillflows", []) if isinstance(part, dict) and isinstance(part.get("modeling_skillflows"), list) else []: result.append(_standard_ref(flow, "part", path, category)) return [item for item in result if item.standard_id] def _standard_ref(flow: dict[str, Any], kind: str, framework_path: Path, category: str = "") -> StandardAnswer: title = str(flow.get("skillflow_name") or flow.get("name") or "") source_path = str(flow.get("path") or "") text = f"{title} {source_path} {category}" return StandardAnswer( standard_id=make_standard_id(f"{kind}-{title or source_path}"), title=title or source_path, kind=_standard_kind(kind), source_path=source_path, source_type="framework_ref", tags=tags_for_text(text), summary=f"Knowledge-base standard answer reference from {framework_path.name}.", steps=[], raw={"framework_path": str(framework_path), "category": category, "ref": flow}, ) def score_standard(tokens: list[str], item: StandardAnswer) -> int: text = " ".join( [ item.standard_id, item.title, item.summary, item.source_path, " ".join(item.tags), " ".join(step.skill + " " + step.name + " " + step.source_feature for step in item.steps[:80]), ] ).lower() return sum(text.count(token) for token in tokens) def _tokens(query: str) -> list[str]: lowered = query.lower() raw = [item for item in lowered.replace("_", " ").replace("-", " ").split() if len(item) >= 2] for index in range(len(lowered) - 1): pair = lowered[index : index + 2] if any("\u4e00" <= ch <= "\u9fff" for ch in pair): raw.append(pair) seen: set[str] = set() return [item for item in raw if item not in seen and not seen.add(item)] def _looks_like_skillflow_name(name: str) -> bool: lowered = name.lower() return "skillflow" in lowered or "skill_flow" in lowered or "modeling_plan" in lowered or "knowledge" in lowered def _standard_kind(value: str) -> StandardKind: try: return StandardKind(value) except ValueError: return StandardKind.unknown