vicTop-cw/fist-mbt/src/evolve does not have a README file

    Archive

    pub struct Archive {
    items : Map[String, Artifact]
    path : String
    }

    档案库(进程内 Map 存储,持久化由调用方决定)。

    Archive::add

    fn Archive::add(self : Archive, a : Artifact) -> Unit

    添加产物。若 parent 已在库中,累加其 children 计数。 重复添加同一 id(覆盖已有记录)时不再重复累加父节点 children,避免计数虚高。

    Archive::all

    fn Archive::all(self : Archive) -> Array[Artifact]

    全部产物(值数组)。

    Archive::best

    fn Archive::best(self : Archive) -> Artifact?

    最高分产物(空库 None)。

    Archive::dead_ends

    fn Archive::dead_ends(self : Archive, limit? : Int) -> Array[String]

    低分产物(“此路不通”地图):喂回给课程生成器绕开。

    Archive::get

    fn Archive::get(self : Archive, id : String) -> Artifact?

    按 id 取产物。

    Archive::is_duplicate

    fn Archive::is_duplicate(self : Archive, goal : String, threshold? : Double) -> Bool

    查重:某个 goal 是否与库中已有产物高度相似(Jaccard >= threshold)。

    Archive::is_empty

    fn Archive::is_empty(self : Archive) -> Bool

    是否为空档案库。

    Archive::len

    fn Archive::len(self : Archive) -> Int

    产物总数。

    Archive::lineage

    fn Archive::lineage(self : Archive, from : String, depth? : Int) -> Array[(String, Double, String)]

    从某产物沿 parent 链回溯谱系(最多 depth 层),返回 (id, score, goal) 数组。

    Archive::new

    fn Archive::new(path : String) -> Archive

    新建档案库。

    Archive::novelty

    fn Archive::novelty(self : Archive, note : String) -> Double

    新颖性:1 - 与库中 note 的最大 Jaccard 重合。值越高越没见过。

    Archive::sample_parent

    fn Archive::sample_parent(self : Archive, lam? : Double, a0? : Double, rand~ : () -> Double) -> Artifact?

    加权滚轮采样:p_i ∝ s_i · h_i,返回选中产物(空库 None)。 rand 注入 () -> Double([0,1) 均匀),保证可测试。

    参数 lam(BUG-354 出路 c):softmax 温度形参,本函数体内不参与计算—— 性能分量实际用 saturating 映射 score / (score + a0)(单调、无需 exp), 所以 lam 是历史残留形状。保留形参是为 pub 契约稳定(删它会动 .mbti 与 调用点);若将来改用真正的 softmax 权重,lam 才是它的入口。

    Archive::summaries

    fn Archive::summaries(self : Archive, limit? : Int) -> Array[String]

    摘要(高分优先):给课程生成器看的 top-N。

    Artifact

    pub struct Artifact {
    id : String
    parent_id : String?
    goal : String
    note : String
    code : String
    score : Double
    parts : Map[String, Double]
    children : Int
    created_at : String
    }

    档案库中的一个产物。

    Artifact::create

    fn Artifact::create(id~ : String, parent_id~ : String?, goal~ : String, note~ : String, code~ : String, score~ : Double, parts~ : Map[String, Double], created_at~ : String) -> Artifact

    构造一个产物(供外部包创建后加入档案库)。

    Artifact::from_json

    fn Artifact::from_json(j : Json) -> Artifact

    从 JSON 反序列化 Artifact。

    Artifact::to_json

    fn Artifact::to_json(self : Artifact) -> Json

    产出 JSON(供 MCP / 展示)。

    Artifact::with_children

    fn Artifact::with_children(a : Artifact, n : Int) -> Artifact

    返回 children 字段更新后的副本(MoonBit struct 不可变,用于累加子代数)。

    CurriculumItem

    pub struct CurriculumItem {
    level : CurriculumLevel
    goal : String
    min_score : Double
    }

    课程条目:一个阶段 + 该阶段的学习目标

    CurriculumLevel

    pub enum CurriculumLevel {
    L1
    L2
    L3
    L4
    } derive(Eq,
    Debug
    )

    课程阶段:从 L1(入门)到 L4(精通),由易到难

    CurriculumLevel::equal

    CurriculumLevel::not_equal

    fn CurriculumLevel::not_equal(x : CurriculumLevel, y : CurriculumLevel) -> Bool

    GhCode

    pub struct GhCode {
    repository : String
    path : String
    html_url : String
    score : Double
    }

    GitHub 代码搜索结果项

    GhRepo

    pub struct GhRepo {
    full_name : String
    description : String
    stargazers_count : Int
    language : String
    html_url : String
    topics : Array[String]
    }

    GitHub 仓库搜索结果项

    MutationKind

    pub enum MutationKind {
    Rewrite
    Simplify
    Generalize
    Specialize
    Combine
    } derive(Eq,
    Debug
    )

    变异算子类型

    MutationKind::equal

    MutationKind::not_equal

    fn MutationKind::not_equal(x : MutationKind, y : MutationKind) -> Bool

    SearchCandidate

    pub struct SearchCandidate {
    name : String
    title : String
    source : String
    mechanism : String
    portable : Bool
    }

    搜索候选:一次外部调研产出的「可移植资产」候选。

    • name:候选名(去重键分量一,如库名/论文名)
    • title:展示标题
    • source:来源(去重键分量二,如 "crate" / "gh" / "paper" / "blogs")
    • mechanism:实现机制/原理描述(供可移植性启发式评估)
    • portable:是否可移植(assess_portability 输出)

    SkillEntry

    pub struct SkillEntry {
    name : String
    description : String
    source_artifact_id : String
    score : Double
    tags : Array[String]
    }

    技能库条目:从高分 artifact 蒸馏出的可复用技能

    analyze_candidate

    fn analyze_candidate(name : String, title : String, source : String, mechanism : String) -> SearchCandidate

    基础清洗:对五个字段做 trim;portable 依据 mechanism 的启发式(assess_portability)。 去重键 = (trimmed name, trimmed source)。

    assess_portability

    fn assess_portability(c : SearchCandidate) -> Bool

    可移植性启发式评估(粗糙但可复现,规则写死在此注释):

    • mechanism 非空;
    • 长度合理:长度 ≥ 4(过短视为信息不足,不可移植);
    • 含可移植性动作词(大小写不敏感):port / portable / porting / adapt / wrapper / wrap / reuse / extract / portability / 移植 / 封装 / 复用 / embed / integrate / conforms / compat / decouple;

    满足全部三条 → true;否则 false。该评估是计算而非 LLM 自评,供 distill 过滤硬门槛。

    can_graduate

    fn can_graduate(current_score : Double, current_level : CurriculumLevel) -> Bool

    判断是否可以从当前阶段"毕业"进入下一阶段

    candidates_to_tsv_lines

    fn candidates_to_tsv_lines(cands : Array[SearchCandidate]) -> String

    把 SearchCandidate 数组转成 TSV 行文本(name\ttitle\tsource\tmechanism), 供 run_external_search 的行解析器消费。纯计算,不碰 IO。
    • portable=false 的候选也输出(让下游 distill 决定过滤,不在这里硬编码门槛)
    • 字段内含 Tab 替换为空格,防止破坏行解析

    code_search_to_tsv

    fn code_search_to_tsv(json_text : String) -> String

    端到端:GitHub 代码搜索 JSON → TSV 行

    code_search_url

    fn code_search_url(query : String, per_page? : Int) -> String

    构造 GitHub 代码搜索 API URL

    code_to_candidates

    fn code_to_candidates(codes : Array[GhCode]) -> Array[SearchCandidate]

    把 GhCode 数组转成 self_search 的 SearchCandidate 数组
    • name = repository:path
    • source = "gh-code"
    • mechanism = path(供可移植性评估,含 "extract" 等词时可判可移植)

    coverage

    fn coverage(a : Array[String], b : Array[String]) -> Double

    覆盖率 |a∩b| / |a|——非对称,a 是基准(根目标),b 是待测文本。 Jaccard 用并集做分母,长文本天然被稀释:子任务多写 200 字模板就"更像漂移", 那是长度惩罚不是目标偏离。coverage 只问"根目标里的词还在不在",多写不罚。

    critic_dup_sim

    fn critic_dup_sim() -> Double

    相似度 ≥ 该值判为"疑似重复/课程漂移",直接建议 reject。 经验阈值:Jaccard 词集重合 0.70 足可视为同一路线。

    critic_most_similar

    fn critic_most_similar(arc : Archive, note : String) -> (String, String, Double)?

    在档案库中找与候选 note 最相似的既有资产,返回 (id, note, 相似度)。 找不到(空库/候选空)返回 None。

    critic_review

    fn critic_review(arc : Archive, note : String, score : Double, threshold? : Double) -> Json

    Critic 综合评审(SAGE Critic 角色,纯计算):
    • novelty = Archive::novelty(note) (相对档案库新鲜度 [0,1])
    • sim = 与最相似既有资产的重合度(最相似者)
    • combined = score==0.5(未计分)→ novelty;否则 0.5score + 0.5novelty (BUG-60:中性 0.5 不参与加权,否则 combined 上限 0.75 永远够不到默认阈值 0.85)
    • admit = sim < dup_sim 且 score_accept(combined, threshold) 返回决策 JSON:{ admit, novelty, score, combined, threshold, sim, rank, reason, top_hit? }。
    fn curl_repo_search(query : String, token? : String) -> String

    生成 curl 命令(供外部执行,不内置网络)
    • token 留空则不带 Authorization header(匿名,限流 60/h)
    • 不回显 token 本体

    curriculum_progress

    fn curriculum_progress(current_score : Double) -> Double

    课程进度:已达阶段 / 总阶段数(0.0 ~ 1.0)

    decision_to_string

    fn decision_to_string(level : CurriculumLevel, mutation : MutationKind, distillable : Bool) -> String

    把 evolve_decision 转可读字符串

    derive_skill_name

    fn derive_skill_name(_goal : String, note : String) -> String

    从 artifact 的 goal + note 生成 skill 名称

    distill_candidates

    fn distill_candidates(cands : Array[SearchCandidate]) -> Array[SearchCandidate]

    蒸馏:去重(同 name+source 只留首个)+ 只保留 portable 的 + 按 source 有序输出。 「输出有序」依 source 字符串升序(字典序)排列,稳定(同 source 保持原始先后)。
    fn distill_code_search(json_text : String) -> Array[SearchCandidate]

    一站式:把代码搜索 JSON 响应直接蒸馏为可移植候选
    fn distill_repo_search(json_text : String) -> Array[SearchCandidate]

    一站式:把仓库搜索 JSON 响应直接蒸馏为可移植候选 (parse → to_candidates → distill)

    distill_skill

    fn distill_skill(artifact_id : String, goal : String, note : String, score : Double) -> SkillEntry?

    从 artifact 蒸馏出 skill 条目

    evolve_decision

    fn evolve_decision(current_score : Double) -> (CurriculumLevel, MutationKind, Bool)

    课程 + 变异 + 蒸馏的一体化决策:给定当前评分,返回 (建议阶段, 建议变异算子, 是否可蒸馏)

    evolve_lesson_persist

    fn evolve_lesson_persist(store :
    SqliteStore
    ?, id~ : String, cat : String, reason : String, fix : String, now : String, score? : Double) -> Bool

    把一条教训持久化为 [lesson] 资产(域无关,仅落库 evolve_artifacts)。
    • store 为数据库句柄(None = 内存态,返回 false)
    • id 由调用方生成并保证趋唯一(重复 id 走 upsert 覆盖,避免多轮打回互相覆盖)
    • note 存失败原因、code 存纠偏提示(为空则取 reason) 返回是否成功落库。

    github_json_to_tsv

    fn github_json_to_tsv(json_text : String) -> String

    把 run_external_search 的原始输出(curl 返回的 GitHub JSON 文本) 直接转成 self_search 可消费的 TSV 行。
    • 自动判断是仓库搜索还是代码搜索 JSON(看 items[0] 是否含 full_name 顶层字段)
    • 解析失败或空 items → 返回空串(下游 distill 得到空数组,零回归)

    is_distillable

    fn is_distillable(score : Double, note : String) -> Bool

    判断 artifact 是否值得蒸馏为 skill(评分 ≥ 0.85 且非空 note)

    is_duplicate_candidate

    fn is_duplicate_candidate(a : SearchCandidate, b : SearchCandidate) -> Bool

    判重:候选去重键一致(name 与 source 均相等)即视为重复。

    is_robust

    fn is_robust(score : Double, floor? : Double) -> Bool

    稳健性谓词:score 不低于 floor(默认 0.70)视为稳健产物。 用于在采样/蒸馏前过滤掉脆弱产物。

    jaccard

    fn jaccard(a : Array[String], b : Array[String]) -> Double

    两个 token 数组的 Jaccard 相似度 |a∩b| / |a∪b|。

    lesson_goal

    fn lesson_goal(cat : String) -> String

    构造 lesson 类目目标串:goal 前加 [lesson] 前缀(幂等,已有前缀则原样返回)。

    lesson_ts_part

    fn lesson_ts_part(now : String) -> String

    从时间戳提取 id 片段(去分隔符后保留到「秒」),用于生成可读且趋唯一的 lesson id。

    R148 一次修掉两个缺陷(都在下面这条链式 replace 上):

    ① replace 只替换第一次出现 ⇒ 分隔符去不干净。实测 "2026-10-28" 走完三个 replace 后是 "202610-28"(第二个 - 还在)、ISO 全串的 : 也只去掉第一个 ⇒ 生成的 id 里 残留 - / :,与函数名与文档承诺("去分隔符")不符。改用 replace_all。 ② 上限 16 切掉秒的个位:修①后 "2026-10-28T12:34:56Z" ⇒ 20261028.12.34.56Z(18 字符), 截 16 ⇒ 20261028.12.34.5 ⇒ 同一 10 秒窗口内两条教训拿到同一个 id;而该 id 是 lesson-<part> 的唯一来源、落库口径是 upsert(重复 id 覆盖) ⇒ 后一条静默覆盖前一条。 改 17(= YYYYMMDD.HH.MM.SS 的长度,正好停在字段边界)。

    mutation_prompt

    fn mutation_prompt(kind : MutationKind, goal : String, note : String) -> String

    对 artifact 的 note/goal 生成变异提示词(供外部执行器使用) 注意:这里只生成"变异指令",真正的代码变异由外部执行器完成

    next_level

    fn next_level(current_score : Double) -> CurriculumLevel

    根据当前评分决定下一步该在哪个阶段学习
    fn parse_code_search(json_text : String) -> Array[GhCode]

    解析 GitHub 代码搜索 API 的 JSON 响应为 GhCode 数组
    fn parse_repo_search(json_text : String) -> Array[GhRepo]

    解析 GitHub 仓库搜索 API 的 JSON 响应为 GhRepo 数组 容错:任何解析失败都跳过该项,不崩溃

    repo_search_to_tsv

    fn repo_search_to_tsv(json_text : String) -> String

    端到端:GitHub 仓库搜索 JSON → TSV 行(可直接喂 run_external_search 的行解析器)

    repo_search_url

    fn repo_search_url(query : String, per_page? : Int, sort? : String) -> String

    构造 GitHub 仓库搜索 API URL query: 搜索关键词(如 "moonbit ltl parser") per_page: 每页条数(1~100,默认 30) sort: 排序字段("stars"/"updated"/"",空=默认 best match)

    repos_to_candidates

    fn repos_to_candidates(repos : Array[GhRepo]) -> Array[SearchCandidate]

    把 GhRepo 数组转成 self_search 的 SearchCandidate 数组
    • name = full_name
    • title = full_name (description 可附在 mechanism)
    • source = "gh"
    • mechanism = description + language + topics(供可移植性评估)
    fn run_external_search(cmd : String, args : Array[String], runner? : (String, Array[String]) -> Result[String, String]?) -> Result[String, String]

    经注入命令执行外部搜索,返回原始文本(成功 Ok / 失败 Err)。

    不内置网络、不硬编码路径;搜索执行器由调用方通过 注入参数 runner 传入 (供上层接入,测试用 mock 注入)。未注入(默认 None)→ Err「未注入搜索执行器」。

    score_accept

    fn score_accept(score : Double, threshold? : Double) -> Bool

    门禁放行:score 达到阈值(默认 0.85)即通过。 供 evolve_submit 落库前 / Omega gate 复用——决策来自可计算分数,而非 LLM 主观判断。

    score_artifact

    fn score_artifact(coverage : Double, fingerprint_ok : Bool, schema_ok : Bool, accuracy : Double) -> Double

    综合评分公式(返回 [0,1]):

    structural = (fingerprint_ok ? 0.25 : 0) + (schema_ok ? 0.25 : 0) // 结构完整分量,max 0.5 completeness = coverage.clamp(0,1) * 0.25 // 覆盖度分量,max 0.25 correctness = accuracy.clamp(0,1) * 0.25 // 正确度分量,max 0.25

    score = clamp(structural + completeness + correctness, 0, 1)

    理由:
    • 结构完整性(fingerprint + schema)是硬前提,合占 0.5。产物若连"可被验证"都满足不了, 即便内容正确也不该排到前列;故两个结构位每个按 0.25 硬惩罚。
    • 覆盖度与正确度各占 0.25,奖励"解得全、解得对",线性映射可复现。
    • 满分 1.0 当且仅当四项全优(coverage=1、fingerprint、schema、accuracy=1)。

    score_of_lesson

    fn score_of_lesson(score : Double) -> Double

    lesson 分数默认 0.5(教训典型中性样本,不参与 best,但参与死路地图权重)。

    score_rank

    fn score_rank(score : Double) -> String

    档位分档(L4 最高),阈值自定、docstring 写明: L4 score ≥ 0.85 —— 优(放行进入 DGM 高分候选) L3 0.70 ≤ score < 0.85 —— 良 L2 0.55 ≤ score < 0.70 —— 中 L1 score < 0.55 —— 弱(档案库仍保留,作谱系垫脚石)

    select_mutation

    fn select_mutation(score : Double, parents? : Int) -> MutationKind

    选择变异算子:根据当前评分档位选择合适的变异策略
    • 低分(L1/L2):偏向 Simplify / Rewrite(先搞对、搞简)
    • 中分(L3):偏向 Generalize / Rewrite(完善结构)
    • 高分(L4):parents < 2 ⇒ Specialize;parents >= 2 ⇒ Combine(融合父代特征)

    BUG-479 / Lane DM(2026-10-29):Combine 过去是文档承诺但永不产出 —— 消费面早就建好了(mutation_prompt / 字符串化都写了 Combine 分支), 但本函数的 4 个分支只返回 Simplify/Rewrite/Generalize/Specialize ⇒ 判例-29 的又一实例(「消费面建好了、生产面没接」)。

    为什么按 parents 修而不是按阈值修: Combine 的语义是「融合两个父代」⇒ 它的前置条件是「有 ≥2 个父代」。 而旧的 select_mutation(score) 只有分数、没有父代信息 ⇒ 它结构上就不可能正确产出 Combine —— 那是「参数面缺信息」, 不是「阈值没调对」。按阈值修只会让它在单父代时也瞎「融合」。

    零回归:parents 缺省 1 ⇒ 现有调用方(只传 score)行为逐字不变。

    standard_curriculum

    fn standard_curriculum() -> Array[CurriculumItem]

    生成标准四阶段课程

    suggest_next_goal

    fn suggest_next_goal(current_score : Double) -> String

    给定当前评分,返回建议的下一阶段目标描述

    tag_skill

    fn tag_skill(skill : SkillEntry, keywords : Array[String]) -> SkillEntry

    给 skill 打标签(从 description 关键词提取)

    tokens

    fn tokens(s : String) -> Array[String]

    切词单真源(BUG-25 标定后重写):ASCII 词 + CJK 单字 + CJK 相邻二字组,去重。

    旧口径是"可见字符集"——对中文等价于逐字拆散,熔断器三态 与 三态熔断器 同分, 而真正的病灶是改写型子任务:子任务合法地换一套词("依赖图恒空"→"增量编译链修复"), 字符集重合因此趋 0,绝对阈值下 15/15 全判漂移(实测见 scripts/calibrate_goal_drift.py)。 新口径加 ASCII 词与 CJK 二字组,让"同词/同词组"才计分: 全量父子对(1222 条)假阳性 16.9%→1.9%,随机错配对照仍 81.3% 判可疑。 口径必须与 scripts/calibrate_goal_drift.py 逐字对齐——数字不是抄来的,是那条命令跑出来的。

    topic_search_url

    fn topic_search_url(topic : String, per_page? : Int) -> String

    构造 GitHub topic 搜索 API URL

    unigrams

    fn unigrams(s : String) -> Array[String]

    可见字符去重集(旧 tokens 口径,保留给兄弟冗余用)。 兄弟重复的典型形状是"词序换位"(数据采集模块 ↔ 采集数据模块), 二字组口径会把它们放行(jaccard 0.556),字符集恰好对词序不敏感(1.0); 实测这个分量在真实非模板家族上不额外增加误报(两种口径同为 34.8%,见标定脚本)。