holoaeon / scripts /build_skill_matrix.py
weideyouni's picture
sync: five-site DNA + B0 baseline freeze (92fd8b5)
9f075f3 verified
Raw History Blame Contribute Delete
4.97 kB
# -*- coding: utf-8 -*-
r"""
build_skill_matrix.py —— 全量技能索引 + F 盘以往项目基座清单(只读扫描,确定性输出)
================================================================================
用法:
python scripts/build_skill_matrix.py # 默认 5 个技能根 + F:\doubaowork
python scripts/build_skill_matrix.py --roots A B # 指定技能根
python scripts/build_skill_matrix.py --projects P # 指定以往项目目录
python scripts/build_skill_matrix.py --json out.json # 额外打印摘要到文件
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from typing import Dict, List
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from holoaeon.skill_matrix import DEFAULT_INDEX, default_roots # noqa: E402
from holoaeon.skill_matrix import index as Ix # noqa: E402
from holoaeon.skill_matrix.mech import semantic_overlap # noqa: E402
from holoaeon.skill_matrix.index import MECH_LEXICON # noqa: E402
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BASELINE = os.path.join(ROOT, "data", "skill_matrix_baseline.json")
SKIP_PREFIX = (".", "_")
FILE_CAP = 4000
def dir_stats(path: str) -> Dict[str, object]:
files = py = 0
skill_md = 0
for base, dirs, names in os.walk(path):
if files >= FILE_CAP:
break
dirs[:] = [d for d in dirs if not d.startswith(".")]
for n in names:
files += 1
if n.endswith(".py"):
py += 1
elif n == "SKILL.md":
skill_md += 1
return {"files": files, "py_files": py, "skill_md": skill_md, "capped": files >= FILE_CAP}
def mech_guess(name: str) -> str:
best, score = "unmapped", 0.0
for key, words in MECH_LEXICON.items():
sc = max((semantic_overlap(name, w) for w in words), default=0.0)
if sc > score:
best, score = key, sc
return best
def scan_projects(root: str) -> List[dict]:
rows: List[dict] = []
if not os.path.isdir(root):
return rows
for entry in sorted(os.listdir(root)):
p = os.path.join(root, entry)
if entry.startswith(SKIP_PREFIX) or not os.path.exists(p):
continue
is_dir = os.path.isdir(p)
stat = os.stat(p)
if is_dir:
ds = dir_stats(p)
kind = "dir"
py = ds["py_files"]
files = ds["files"]
else:
kind = "zip" if entry.lower().endswith(".zip") else (
"md" if entry.lower().endswith(".md") else "file")
py, files = 0, 1
rows.append({"name": entry, "kind": kind,
"bytes": int(stat.st_size) if not is_dir else 0,
"files": files, "py_files": py,
"mechanism": mech_guess(entry),
"tier": "基座候选" if (py >= 10 or files >= 50) else "素材"})
return rows
def main(argv=None) -> int:
ap = argparse.ArgumentParser(description="构建技能索引与基座清单")
ap.add_argument("--roots", nargs="*", default=None)
ap.add_argument("--projects", default=r"F:\doubaowork")
ap.add_argument("--index", default=DEFAULT_INDEX)
ap.add_argument("--baseline", default=BASELINE)
ap.add_argument("--json", default=None)
a = ap.parse_args(argv)
roots = a.roots or default_roots()
idx = Ix.build(roots)
sha = Ix.save(idx, a.index)
base_rows = scan_projects(a.projects)
base = {"source": os.path.abspath(a.projects),
"count": len(base_rows), "entries": base_rows,
"summary": {"dirs": sum(1 for r in base_rows if r["kind"] == "dir"),
"zips": sum(1 for r in base_rows if r["kind"] == "zip"),
"md": sum(1 for r in base_rows if r["kind"] == "md"),
"baseline_candidates": sum(1 for r in base_rows if r["tier"] == "基座候选"),
"py_total": sum(int(r["py_files"]) for r in base_rows)}}
os.makedirs(os.path.dirname(a.baseline), exist_ok=True)
payload = json.dumps(base, ensure_ascii=False, indent=1, sort_keys=True)
with open(a.baseline, "w", encoding="utf-8", newline="\n") as fh:
fh.write(payload)
summary = {"index": {"path": a.index, "digest": idx["digest"], "count": idx["count"],
"sha16": sha, "roots": len(idx["roots"])},
"stats": idx["stats"],
"baseline": dict(base["summary"], path=a.baseline,
sha16=__import__("hashlib").sha256(payload.encode("utf-8")).hexdigest()[:16])}
print(json.dumps(summary, ensure_ascii=False, indent=1))
if a.json:
with open(a.json, "w", encoding="utf-8", newline="\n") as fh:
fh.write(json.dumps(summary, ensure_ascii=False, indent=1))
return 0
if __name__ == "__main__":
sys.exit(main())