#!/usr/bin/env python3"""scan_sampling.py — 非推奨サンプリング引数の依存を、宣言ではなく「影響を受ける呼び出し箇所」で数える。"""import reimport sysfrom pathlib import PathSAMPLING_KEYS = ("temperature", "top_p", "topP", "top_k", "topK")CODE_EXT = {".py", ".ts", ".tsx", ".js", ".mjs"}CONF_EXT = {".json", ".yaml", ".yml", ".toml"}CALL_RE = re.compile(r"generate_?[Cc]ontent")CARRIER_RE = [ re.compile(r"^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)\s*[:=]", re.M), re.compile(r"^\s*([A-Z][A-Z0-9_]*)\s*=", re.M), # Python 定数 re.compile(r"^\s*(?:export\s+)?function\s+([A-Za-z_]\w*)", re.M), re.compile(r"^\s*def\s+([a-z_]\w*)", re.M),]def carriers_in(text, seeds): """サンプリング引数、または既知の運び手を含むブロックから 外へ配られる識別子名を抽出する。""" found = set() for rx in CARRIER_RE: for m in rx.finditer(text): name = m.group(1) tail = text[m.start(): m.start() + 400] if any(k in tail for k in SAMPLING_KEYS): found.add(name) elif any(re.search(rf"\b{re.escape(s)}\b", tail) for s in seeds): found.add(name) # 運び手を包み直しただけの層も運び手 return founddef scan(root): decl, conf, carriers = [], [], set() files = [p for p in root.rglob("*") if p.is_file()] for p in files: if p.suffix not in CODE_EXT | CONF_EXT: continue text = p.read_text(encoding="utf-8", errors="ignore") hits = [k for k in SAMPLING_KEYS if k in text] if not hits: continue rel = str(p.relative_to(root)) if p.suffix in CONF_EXT: conf.append((rel, sorted(set(hits)))) else: decl.append((rel, sorted(set(hits)))) carriers |= carriers_in(text, set()) # 不動点まで伝播させる(defaults -> buildConfig -> cfg の包み直しを追う) code_files = [(p, p.read_text(encoding="utf-8", errors="ignore")) for p in files if p.suffix in CODE_EXT] for _ in range(5): grown = set(carriers) for _p, text in code_files: grown |= carriers_in(text, carriers) if grown == carriers: break carriers = grown direct, indirect = [], [] for p in files: if p.suffix not in CODE_EXT: continue text = p.read_text(encoding="utf-8", errors="ignore") lines = text.splitlines() rel = str(p.relative_to(root)) for i, line in enumerate(lines, 1): if not CALL_RE.search(line): continue window = "\n".join(lines[max(0, i - 6): i + 6]) if any(k in window for k in SAMPLING_KEYS): direct.append(f"{rel}:{i}") elif any(re.search(rf"\b{re.escape(n)}\b", window) for n in carriers): indirect.append(f"{rel}:{i}") return decl, conf, sorted(carriers), direct, indirectif __name__ == "__main__": root = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve() decl, conf, carriers, direct, indirect = scan(root) print(f"[declarations] code : {len(decl)}") for rel, ks in decl: print(f" - {rel} {','.join(ks)}") print(f"[declarations] config : {len(conf)}") for rel, ks in conf: print(f" - {rel} {','.join(ks)}") print(f"[carriers] : {len(carriers)} -> {', '.join(carriers)}") print(f"[call sites] 直接 : {len(direct)} -> {', '.join(direct)}") print(f"[call sites] 間接 : {len(indirect)} -> {', '.join(indirect)}") print(f"[total] 移行対象 : {len(direct) + len(indirect)}")
def build_balanced(intent, n, seed=0): """重複を出さずに水準の偏りを最小化する。""" keys, levels = list(AXES), list(AXES.values()) pool = list(itertools.product(*levels)) random.Random(seed).shuffle(pool) # 同点時の順序を seed で固定 used = {k: {v: 0 for v in vals} for k, vals in AXES.items()} chosen = [] for _ in range(min(n, len(pool))): best = min(pool, key=lambda c: (sum(used[k][v] for k, v in zip(keys, c)), pool.index(c))) pool.remove(best) for k, v in zip(keys, best): used[k][v] += 1 cond = "・".join(f"{k}は{v}" for k, v in zip(keys, best)) chosen.append(PREFIX.format(intent=intent) + cond) return chosendef coverage(prompts): """採用した集合が各軸の水準をどれだけ均等に踏んでいるか。""" rep = {} for k, vals in AXES.items(): c = {v: sum(1 for p in prompts if f"{k}は{v}" in p) for v in vals} rep[k] = (c, max(c.values()) - min(c.values())) return rep
#!/usr/bin/env python3"""diversity_gate.py — 言い換え候補の実効多様度を測る二層ゲート。"""import hashlibimport reimport sysimport unicodedataSIMHASH_BITS = 64HAMMING_MAX = 12 # これ以下の距離は「同じ言い方」とみなすJACCARD_MAX = 0.72 # これ以上重なるトークン集合は冗長def normalize(s): s = unicodedata.normalize("NFKC", s).lower() return re.sub(r"[\s、。,.!?!?「」『』()()・\-—]+", "", s)def shingles(s, n=2): s = normalize(s) return [s[i:i + n] for i in range(max(len(s) - n + 1, 1))]def simhash(s): v = [0] * SIMHASH_BITS for sh in shingles(s): h = int.from_bytes(hashlib.blake2b(sh.encode(), digest_size=8).digest(), "big") for i in range(SIMHASH_BITS): v[i] += 1 if (h >> i) & 1 else -1 out = 0 for i, x in enumerate(v): if x > 0: out |= 1 << i return outdef hamming(a, b): return bin(a ^ b).count("1")def tokens(s): # 形態素解析器を持ち込まずに済ませる近似: 文字 trigram を語の代用にする t = normalize(s) return {t[i:i + 3] for i in range(max(len(t) - 2, 1))}def jaccard(a, b): return len(a & b) / len(a | b) if (a | b) else 0.0def gate(cands): kept, dropped = [], [] for c in cands: h, tk = simhash(c), tokens(c) clash = next((k for k in kept if hamming(h, k[1]) <= HAMMING_MAX or jaccard(tk, k[2]) >= JACCARD_MAX), None) if clash: dropped.append((c, clash[0])) else: kept.append((c, h, tk)) return [k[0] for k in kept], droppedif __name__ == "__main__": cands = [l.rstrip("\n") for l in sys.stdin if l.strip()] kept, dropped = gate(cands) print(f"入力候補 : {len(cands)}") print(f"生存(実効多様度) : {len(kept)} = {len(kept) / len(cands):.0%}") for c in kept: print(f" ✓ {c}") for c, by in dropped: print(f" ✗ {c} ← 既出: {by}")