灵符lingfu
L

ling-miao

↓ 0 ダウンロード

miao: a Ling-native version control system — temples, souls, rites and talismans instead of repos, branches, commits and tags; adaptive whole/delta/chunk storage; SSH temple fairs for sharing; a LingStationX-themed GUI; ROYGBIV everywhere

Windows · x86-64macOS · Intel x86-64Linux · x86-64LingOS · Pi 5 ARM64
キーワード vcs, version-control, git, miao, ling
lingfu install ling-miao
ホームページhttps://ling-lang.org
ライセンスApache-2.0 OR MIT
作者ling-lin
READMEコード / ファイルバージョン依存グラフ依存パッケージセキュリティビルド設定

コード / ファイル (268)

ling/rites.ling
# ━━━ rites.ling — the ceremonies: divine, offer, rite, lineage, summon… ━━━
# fns prefixed rt_. These implement the miao commands on top of store.ling.

# ── path resolution (user arg -> repo-relative path) ──────────────────
fn rt_relpath(arg) {
    bind p = ut_norm(arg)
    if str_starts_with(p, "./") { bind p = str_slice(p, 2.0, 99999.0) }
    bind pre = sx_prefix()
    bind cand = p
    if pre != "" { bind cand = pre + "/" + p }
    if file_exists(sx_root() + "/" + cand) { return cand }
    if file_exists(sx_root() + "/" + p) { return p }
    return cand
}

# any ref: soul | talisman | rite | mother/soul (fetched tip)
fn rt_resolve(name) {
    bind r = sx_resolve(name)
    if r != "" { return r }
    # mother tips: lines "mother|soul|rite", referenced as "mother/soul"
    bind lines = ut_read_lines(sx_dir() + "/mtips")
    bind i = 0.0
    while i < len(lines) {
        bind parts = split(list_get(lines, i), "|")
        bind full = list_get(parts, 0.0) + "/" + list_get(parts, 1.0)
        if full == name { return list_get(parts, 2.0) }
        bind i = i + 1.0
    }
    return ""
}

# ── divine (status) ───────────────────────────────────────────────────
# returns [altar_paths, altar_states, restless_paths, restless_states, wandering]
fn rt_status_data() {
    bind head = sx_rite_manifest(sx_head_rite())
    bind hids = list_get(head, 0.0)
    bind hpaths = list_get(head, 1.0)
    bind a = sx_altar()
    bind aids = list_get(a, 0.0)
    bind apaths = list_get(a, 1.0)
    bind work = sx_walk()
    bind sp = list_new()
    bind ss = list_new()
    bind i = 0.0
    while i < len(apaths) {
        bind p = list_get(apaths, i)
        bind id = list_get(aids, i)
        bind hat = ut_index_of(hpaths, p)
        if id == "-" {
            bind sp = list_push(sp, p)
            bind ss = list_push(ss, "gone")
        } else if hat < 0.0 {
            bind sp = list_push(sp, p)
            bind ss = list_push(ss, "new")
        } else if list_get(hids, hat) != id {
            bind sp = list_push(sp, p)
            bind ss = list_push(ss, "edit")
        } else {
            bind sp = list_push(sp, p)
            bind ss = list_push(ss, "same")
        }
        bind i = i + 1.0
    }
    # restless: worktree differs from altar/head
    bind rp = list_new()
    bind rs = list_new()
    bind wander = list_new()
    bind i = 0.0
    while i < len(work) {
        bind p = list_get(work, i)
        bind aat = ut_index_of(apaths, p)
        bind hat = ut_index_of(hpaths, p)
        bind expected = ""
        if aat >= 0.0 {
            bind expected = list_get(aids, aat)
        } else if hat >= 0.0 {
            bind expected = list_get(hids, hat)
        }
        if expected == "" {
            bind wander = list_push(wander, p)
        } else if expected == "-" {
            # staged for removal but still present: wandering again
            bind wander = list_push(wander, p)
        } else {
            bind actual = file_hash(sx_root() + "/" + p)
            if actual != expected {
                bind rp = list_push(rp, p)
                bind rs = list_push(rs, "edit")
            }
        }
        bind i = i + 1.0
    }
    # departed: tracked or staged, but missing from the worktree
    bind i = 0.0
    while i < len(hpaths) {
        bind p = list_get(hpaths, i)
        if ut_contains(work, p) == false {
            bind aat = ut_index_of(apaths, p)
            bind staged_gone = false
            if aat >= 0.0 {
                if list_get(aids, aat) == "-" { bind staged_gone = true }
            }
            if staged_gone == false {
                bind rp = list_push(rp, p)
                bind rs = list_push(rs, "gone")
            }
        }
        bind i = i + 1.0
    }
    bind i = 0.0
    while i < len(apaths) {
        bind p = list_get(apaths, i)
        if list_get(aids, i) != "-" {
            if ut_contains(work, p) == false {
                if ut_contains(hpaths, p) == false {
                    bind rp = list_push(rp, p)
                    bind rs = list_push(rs, "gone")
                }
            }
        }
        bind i = i + 1.0
    }
    return [sp, ss, rp, rs, wander]
}

fn rt_divine() {
    bind h = sx_head()
    bind tip = sx_head_rite()
    bind where = ""
    if list_get(h, 0.0) == "soul" {
        bind where = cx_info("soul " + list_get(h, 1.0))
    } else {
        bind where = cx_warn("wandering rite " + ut_short(list_get(h, 1.0))) + cx_dim(" (detached)")
    }
    bind tipstr = cx_dim("newborn temple — no rites yet")
    if tip != "" {
        bind r = sx_rite_read(tip)
        bind tipstr = cx_aura(tip) + " " + cx_dim(ut_first_line(list_get(r, 4.0)))
    }
    print(cx_rainbow("miao") + "  " + where + cx_dim(" · ") + tipstr)
    bind d = rt_status_data()
    bind sp = list_get(d, 0.0)
    bind ss = list_get(d, 1.0)
    bind rp = list_get(d, 2.0)
    bind rs = list_get(d, 3.0)
    bind wander = list_get(d, 4.0)
    bind shown = 0.0
    if len(sp) > 0.0 {
        print("")
        print(cx_stage("on the altar") + cx_dim("  (sealed by the next rite — `miao withdraw <path>` to take back)"))
        bind i = 0.0
        while i < len(sp) {
            bind s = list_get(ss, i)
            bind tag = "offered "
            if s == "new" { bind tag = "new     " }
            if s == "gone" { bind tag = "gone    " }
            if s == "edit" { bind tag = "renewed " }
            print("  " + cx_stage(tag) + cx_stage(list_get(sp, i)))
            bind i = i + 1.0
            bind shown = shown + 1.0
        }
    }
    if len(rp) > 0.0 {
        print("")
        print(cx_warn("restless spirits") + cx_dim("  (changed since enshrined — `miao offer <path>` to stage)"))
        bind i = 0.0
        while i < len(rp) {
            bind tag = "changed "
            if list_get(rs, i) == "gone" { bind tag = "departed" }
            print("  " + cx_warn(tag) + " " + cx_warn(list_get(rp, i)))
            bind i = i + 1.0
            bind shown = shown + 1.0
        }
    }
    if len(wander) > 0.0 {
        print("")
        print(cx_tal("wandering spirits") + cx_dim("  (untracked — `miao offer <path>` to enshrine)"))
        bind i = 0.0
        while i < len(wander) {
            print("  " + cx_tal("        " + list_get(wander, i)))
            bind i = i + 1.0
            bind shown = shown + 1.0
        }
    }
    if file_exists(sx_dir() + "/DISCORD") {
        print("")
        print(cx_err("⚠ discord in the temple — resolve the marked files, offer them, then seal with `miao rite`"))
    } else if shown < 0.5 {
        print(cx_good("the temple is serene — nothing to divine"))
    }
    return 0.0
}

# ── offer (stage) ─────────────────────────────────────────────────────
fn rt_offer_one(rel) {
    bind abs = sx_root() + "/" + rel
    bind head = sx_rite_manifest(sx_head_rite())
    bind hids = list_get(head, 0.0)
    bind hpaths = list_get(head, 1.0)
    bind hat = ut_index_of(hpaths, rel)
    if file_exists(abs) == false {
        if hat >= 0.0 {
            sx_altar_set(rel, "-")
            print("  " + cx_err("⊖ released ") + rel)
            return 1.0
        }
        print("  " + cx_err("no such spirit: " + rel))
        return 0.0
    }
    bind a = sx_altar()
    bind aat = ut_index_of(list_get(a, 1.0), rel)
    bind prev = ""
    if aat >= 0.0 {
        bind prev = list_get(list_get(a, 0.0), aat)
        if prev == "-" { bind prev = "" }
    }
    if prev == "" {
        if hat >= 0.0 { bind prev = list_get(hids, hat) }
    }
    bind res = sx_spirit_put(abs, prev)
    bind id = list_get(res, 0.0)
    bind strat = list_get(res, 1.0)
    if id == "" {
        print("  " + cx_err("unreadable: " + rel))
        return 0.0
    }
    if hat >= 0.0 {
        if list_get(hids, hat) == id {
            sx_altar_del(rel)
            print("  " + cx_dim("∅ serene   " + rel) + cx_dim("  (matches the enshrined spirit)"))
            return 0.0
        }
    }
    sx_altar_set(rel, id)
    bind glyph = "○ whole  "
    if strat == "delta" { bind glyph = "◐ delta  " }
    if str_starts_with(strat, "chunks") { bind glyph = "◍ " + strat }
    if strat == "kept" { bind glyph = "◉ kept   " }
    print("  " + cx_stage("⊕ offered ") + ut_pad(rel, 36.0) + cx_meta(" " + glyph))
    return 1.0
}

fn rt_offer(args) {
    if len(args) < 1.0 {
        print(cx_err("what shall be offered? try: miao offer <path>  or  miao offer ."))
        return 0.0
    }
    bind count = 0.0
    bind i = 0.0
    while i < len(args) {
        bind argp = list_get(args, i)
        if argp == "." || argp == "--all" || argp == "-A" {
            # everything changed or wandering, plus departures
            bind d = rt_status_data()
            bind rp = list_get(d, 2.0)
            bind wander = list_get(d, 4.0)
            bind q = 0.0
            while q < len(rp) {
                bind count = count + rt_offer_one(list_get(rp, q))
                bind q = q + 1.0
            }
            bind q = 0.0
            while q < len(wander) {
                bind count = count + rt_offer_one(list_get(wander, q))
                bind q = q + 1.0
            }
        } else {
            bind rel = rt_relpath(argp)
            bind abs = sx_root() + "/" + rel
            if is_dir(abs) {
                bind files = sx_walk_dir(abs, rel, list_new())
                bind q = 0.0
                while q < len(files) {
                    bind count = count + rt_offer_one(list_get(files, q))
                    bind q = q + 1.0
                }
            } else {
                bind count = count + rt_offer_one(rel)
            }
        }
        bind i = i + 1.0
    }
    if count > 0.0 {
        print(cx_stage("" + count + " offering(s) on the altar") + cx_dim(" — seal them with `miao rite -m \"...\"`"))
    }
    return 0.0
}

fn rt_withdraw(args) {
    if len(args) < 1.0 {
        print(cx_err("withdraw what? try: miao withdraw <path>"))
        return 0.0
    }
    bind i = 0.0
    while i < len(args) {
        bind rel = rt_relpath(list_get(args, i))
        bind a = sx_altar()
        if ut_index_of(list_get(a, 1.0), rel) >= 0.0 {
            sx_altar_del(rel)
            print("  " + cx_warn("⊖ withdrawn ") + rel)
        } else {
            print("  " + cx_dim("not on the altar: " + rel))
        }
        bind i = i + 1.0
    }
    return 0.0
}

# ── rite (commit) ─────────────────────────────────────────────────────
fn rt_rite(msg) {
    if msg == "" {
        print(cx_err("a rite needs an inscription: miao rite -m \"message\""))
        return 0.0
    }
    bind a = sx_altar()
    bind discord = file_exists(sx_dir() + "/DISCORD")
    if len(list_get(a, 0.0)) < 1.0 {
        if discord == false {
            print(cx_warn("the altar is bare — `miao offer <path>` first"))
            return 0.0
        }
    }
    bind parents = list_new()
    bind head_rite = sx_head_rite()
    if head_rite != "" { bind parents = list_push(parents, head_rite) }
    if discord {
        bind dl = ut_read_lines(sx_dir() + "/DISCORD")
        bind first = list_get(dl, 0.0)
        bind parts = split(first, "|")
        if list_get(parts, 0.0) == "theirs" {
            bind parents = list_push(parents, list_get(parts, 1.0))
        }
    }
    bind ov = sx_overlay()
    bind scroll = sx_scroll_put(list_get(ov, 0.0), list_get(ov, 1.0))
    bind id = sx_rite_put(scroll, parents, sx_author(), msg)
    bind h = sx_head()
    if list_get(h, 0.0) == "soul" {
        sx_set_tip(list_get(h, 1.0), id)
    } else {
        sx_set_head_rite(id)
    }
    sx_altar_write(list_new(), list_new())
    if discord { remove_file(sx_dir() + "/DISCORD") }
    sx_log("rite", id + "|" + ut_first_line(msg))
    bind n = len(list_get(ov, 0.0))
    print(cx_good("✦ rite sealed  ") + cx_aura(id) + cx_dim("  " + n + " spirit(s) enshrined"))
    print(cx_dim("  「") + cx_info(ut_first_line(msg)) + cx_dim("」"))
    if discord { print(cx_good("  harmony restored — the discord is resolved")) }
    return 0.0
}

# ── lineage (log) ─────────────────────────────────────────────────────
fn rt_lineage(limit, all_flag) {
    bind tips = list_new()
    if all_flag {
        bind souls = sx_souls()
        bind i = 0.0
        while i < len(souls) {
            bind t = sx_tip(list_get(souls, i))
            if t != "" {
                if ut_contains(tips, t) == false { bind tips = list_push(tips, t) }
            }
            bind i = i + 1.0
        }
    } else {
        bind t = sx_head_rite()
        if t != "" { bind tips = list_push(tips, t) }
    }
    if len(tips) < 1.0 {
        print(cx_dim("the genealogy is empty — no rites have been sealed"))
        return 0.0
    }
    # gather rites breadth-first following all parents, then sort by time
    bind seen = list_new()
    bind queue = tips
    bind qi = 0.0
    while qi < len(queue) {
        bind id = list_get(queue, qi)
        if ut_contains(seen, id) == false {
            bind seen = list_push(seen, id)
            bind r = sx_rite_read(id)
            bind ps = list_get(r, 1.0)
            bind k = 0.0
            while k < len(ps) {
                bind queue = list_push(queue, list_get(ps, k))
                bind k = k + 1.0
            }
        }
        bind qi = qi + 1.0
    }
    # decorate: souls and talismans per rite
    bind souls = sx_souls()
    bind tals = sx_talismans()
    # sort by time desc (selection on small n is fine; lineage is capped anyway)
    bind times = list_new()
    bind i = 0.0
    while i < len(seen) {
        bind r = sx_rite_read(list_get(seen, i))
        bind times = list_push(times, list_get(r, 3.0))
        bind i = i + 1.0
    }
    bind shown = 0.0
    bind used = list_new()
    bind i = 0.0
    while i < len(seen) {
        bind used = list_push(used, 0.0)
        bind i = i + 1.0
    }
    while shown < limit && shown < len(seen) {
        bind best = 0.0 - 1.0
        bind k = 0.0
        while k < len(seen) {
            if list_get(used, k) < 0.5 {
                if best < 0.0 {
                    bind best = k
                } else if list_get(times, k) > list_get(times, best) {
                    bind best = k
                }
            }
            bind k = k + 1.0
        }
        if best < 0.0 {
            bind shown = limit
        } else {
            bind used = list_set(used, best, 1.0)
            bind id = list_get(seen, best)
            bind r = sx_rite_read(id)
            bind deco = ""
            bind s = 0.0
            while s < len(souls) {
                if sx_tip(list_get(souls, s)) == id { bind deco = deco + " " + cx_info("❮" + list_get(souls, s) + "❯") }
                bind s = s + 1.0
            }
            bind s = 0.0
            while s < len(tals) {
                if sx_talisman_get(list_get(tals, s)) == id { bind deco = deco + " " + cx_tal("符" + list_get(tals, s)) }
                bind s = s + 1.0
            }
            bind merge_mark = ""
            if len(list_get(r, 1.0)) > 1.5 { bind merge_mark = cx_tal(" ☯") }
            print(cx_aura(id) + "  " + cx_meta(ut_fmt_time(list_get(r, 3.0))) + "  " + cx_good(ut_pad(list_get(r, 2.0), 12.0)) + " " + ut_first_line(list_get(r, 4.0)) + merge_mark + deco)
            bind shown = shown + 1.0
        }
    }
    return 0.0
}

# ── yinyang (diff) ────────────────────────────────────────────────────
fn rt_spirit_text_or(id) {
    if id == "" { return "" }
    bind info = sx_spirit_info(id)
    if list_get(info, 4.0) == "bin" { return "" }
    return sx_mat_text(id)
}

fn rt_worktree_text(rel) {
    bind r = file_read_hex(sx_root() + "/" + rel)
    match r {
        bad(e) => { return "" }
        ok(v) => { bind r = r }
    }
    bind t = hex_text(r.unwrap())
    match t {
        bad(e) => { return "" }
        ok(v) => { return t.unwrap() }
    }
}

fn rt_is_bin_spirit(id) {
    if id == "" { return false }
    bind info = sx_spirit_info(id)
    return list_get(info, 4.0) == "bin"
}

fn rt_yinyang(args) {
    bind staged = false
    bind refs = list_new()
    bind path_filter = ""
    bind i = 0.0
    while i < len(args) {
        bind a0 = list_get(args, i)
        if a0 == "--altar" || a0 == "--staged" || a0 == "--cached" {
            bind staged = true
        } else if rt_resolve(a0) != "" {
            bind refs = list_push(refs, rt_resolve(a0))
        } else {
            bind path_filter = rt_relpath(a0)
        }
        bind i = i + 1.0
    }
    if len(refs) >= 2.0 {
        rt_yinyang_rites(list_get(refs, 0.0), list_get(refs, 1.0), path_filter)
        return 0.0
    }
    if len(refs) == 1.0 {
        # ref vs worktree
        rt_yinyang_worktree(list_get(refs, 0.0), path_filter)
        return 0.0
    }
    if staged {
        # altar vs head
        bind head = sx_rite_manifest(sx_head_rite())
        bind hids = list_get(head, 0.0)
        bind hpaths = list_get(head, 1.0)
        bind a = sx_altar()
        bind aids = list_get(a, 0.0)
        bind apaths = list_get(a, 1.0)
        bind i = 0.0
        bind any = 0.0
        while i < len(apaths) {
            bind p = list_get(apaths, i)
            bind okp = true
            if path_filter != "" {
                if p != path_filter { bind okp = false }
            }
            if okp {
                bind nid = list_get(aids, i)
                bind hat = ut_index_of(hpaths, p)
                bind oid = ""
                if hat >= 0.0 { bind oid = list_get(hids, hat) }
                rt_render_pair(p, oid, nid, "")
                bind any = any + 1.0
            }
            bind i = i + 1.0
        }
        if any < 0.5 { print(cx_dim("the altar casts no shadow — nothing staged")) }
        return 0.0
    }
    # default: worktree vs altar+head overlay (unstaged changes)
    rt_yinyang_worktree("", path_filter)
    return 0.0
}

# diff a single file pair by spirit ids; wt_rel != "" means right side is the worktree file
fn rt_render_pair(p, old_id, new_id, wt_rel) {
    bind old_bin = rt_is_bin_spirit(old_id)
    bind new_bin = false
    bind new_txt = ""
    if wt_rel != "" {
        bind new_txt = rt_worktree_text(wt_rel)
        if new_txt == "" {
            if file_exists(sx_root() + "/" + wt_rel) {
                bind hr = file_read_hex(sx_root() + "/" + wt_rel)
                if ut_is_bad(hr) == false {
                    bind new_bin = len(hr.unwrap()) > 0.0
                }
            }
        }
    } else {
        bind new_bin = rt_is_bin_spirit(new_id)
        if new_bin == false { bind new_txt = rt_spirit_text_or(new_id) }
    }
    if old_bin || new_bin {
        print(cx_tal("☯ " + p) + "  " + cx_warn("binary spirits differ"))
        return 0.0
    }
    bind old_txt = rt_spirit_text_or(old_id)
    bind label = p
    if old_id == "" { bind label = p + cx_good("  (newly manifest)") }
    if new_id == "-" {
        print(cx_tal("☯ " + p) + "  " + cx_err("released from the temple"))
        return 0.0
    }
    print_file(dy_render(old_txt, new_txt, label))
    return 0.0
}

fn rt_yinyang_worktree(refid, path_filter) {
    bind base = refid
    if base == "" {
        # overlay = altar + head
        bind ov = sx_overlay()
        bind bids = list_get(ov, 0.0)
        bind bpaths = list_get(ov, 1.0)
        rt_yinyang_against(bids, bpaths, path_filter)
        return 0.0
    }
    bind m = sx_rite_manifest(base)
    rt_yinyang_against(list_get(m, 0.0), list_get(m, 1.0), path_filter)
    return 0.0
}

fn rt_yinyang_against(bids, bpaths, path_filter) {
    bind work = sx_walk()
    bind any = 0.0
    bind i = 0.0
    while i < len(work) {
        bind p = list_get(work, i)
        bind okp = true
        if path_filter != "" {
            if p != path_filter {
                if str_starts_with(p, path_filter + "/") == false { bind okp = false }
            }
        }
        if okp {
            bind at = ut_index_of(bpaths, p)
            if at >= 0.0 {
                bind oid = list_get(bids, at)
                bind actual = file_hash(sx_root() + "/" + p)
                if actual != oid {
                    rt_render_pair(p, oid, "", p)
                    bind any = any + 1.0
                }
            } else {
                print(cx_tal("☯ " + p) + "  " + cx_tal("wandering spirit (untracked)"))
                bind any = any + 1.0
            }
        }
        bind i = i + 1.0
    }
    # departures
    bind i = 0.0
    while i < len(bpaths) {
        bind p = list_get(bpaths, i)
        bind okp = true
        if path_filter != "" {
            if p != path_filter { bind okp = false }
        }
        if okp {
            if file_exists(sx_root() + "/" + p) == false {
                print(cx_tal("☯ " + p) + "  " + cx_err("departed (deleted in the worktree)"))
                bind any = any + 1.0
            }
        }
        bind i = i + 1.0
    }
    if any < 0.5 { print(cx_good("yin and yang are balanced — no differences")) }
    return 0.0
}

fn rt_yinyang_rites(a, b, path_filter) {
    bind ma = sx_rite_manifest(a)
    bind mb = sx_rite_manifest(b)
    bind aids = list_get(ma, 0.0)
    bind apaths = list_get(ma, 1.0)
    bind bids = list_get(mb, 0.0)
    bind bpaths = list_get(mb, 1.0)
    bind any = 0.0
    bind i = 0.0
    while i < len(bpaths) {
        bind p = list_get(bpaths, i)
        bind okp = true
        if path_filter != "" {
            if p != path_filter { bind okp = false }
        }
        if okp {
            bind at = ut_index_of(apaths, p)
            bind oid = ""
            if at >= 0.0 { bind oid = list_get(aids, at) }
            if oid != list_get(bids, i) {
                rt_render_pair(p, oid, list_get(bids, i), "")
                bind any = any + 1.0
            }
        }
        bind i = i + 1.0
    }
    bind i = 0.0
    while i < len(apaths) {
        bind p = list_get(apaths, i)
        bind okp = true
        if path_filter != "" {
            if p != path_filter { bind okp = false }
        }
        if okp {
            if ut_contains(bpaths, p) == false {
                print(cx_tal("☯ " + p) + "  " + cx_err("released between these rites"))
                bind any = any + 1.0
            }
        }
        bind i = i + 1.0
    }
    if any < 0.5 { print(cx_good("yin and yang are balanced — no differences")) }
    return 0.0
}

# ── souls (branches) ──────────────────────────────────────────────────
fn rt_soul(args) {
    bind h = sx_head()
    bind cur = ""
    if list_get(h, 0.0) == "soul" { bind cur = list_get(h, 1.0) }
    if len(args) < 1.0 {
        bind souls = sx_souls()
        bind i = 0.0
        while i < len(souls) {
            bind s = list_get(souls, i)
            bind tip = sx_tip(s)
            bind line = ""
            if s == cur {
                bind line = cx_good("● " + ut_pad(s, 16.0))
            } else {
                bind line = cx_info("○ " + ut_pad(s, 16.0))
            }
            if tip != "" {
                bind r = sx_rite_read(tip)
                bind line = line + " " + cx_aura(tip) + " " + cx_dim(ut_first_line(list_get(r, 4.0)))
            } else {
                bind line = line + " " + cx_dim("(unborn)")
            }
            print(line)
            bind i = i + 1.0
        }
        return 0.0
    }
    bind first = list_get(args, 0.0)
    if first == "-d" || first == "--release" {
        bind name = list_get(args, 1.0)
        if name == cur {
            print(cx_err("cannot release the living soul — summon another first"))
            return 0.0
        }
        if file_exists(sx_dir() + "/souls/" + name) == false {
            print(cx_err("no such soul: " + name))
            return 0.0
        }
        remove_file(sx_dir() + "/souls/" + name)
        print(cx_warn("soul released: " + name))
        return 0.0
    }
    if file_exists(sx_dir() + "/souls/" + first) {
        print(cx_err("that soul already dwells here: " + first))
        return 0.0
    }
    bind at = sx_head_rite()
    if len(args) > 1.5 { bind at = rt_resolve(list_get(args, 1.0)) }
    sx_set_tip(first, at)
    sx_log("soul", first + "|" + at)
    print(cx_good("✦ new soul kindled: ") + cx_info(first) + cx_dim(ut_sel(at == "", " (unborn)", " at " + ut_short(at))))
    print(cx_dim("  summon it with `miao summon " + first + "`"))
    return 0.0
}

# ── summon (checkout / switch) ────────────────────────────────────────
fn rt_summon(args) {
    if len(args) < 1.0 {
        print(cx_err("summon which soul? try: miao summon <soul|rite|talisman>"))
        return 0.0
    }
    bind target = list_get(args, 0.0)
    bind force = false
    bind i = 1.0
    while i < len(args) {
        if list_get(args, i) == "--force" || list_get(args, i) == "-f" { bind force = true }
        bind i = i + 1.0
    }
    bind rite = rt_resolve(target)
    bind is_soul = file_exists(sx_dir() + "/souls/" + target)
    if rite == "" {
        if is_soul == false {
            print(cx_err("no soul, talisman or rite answers to: " + target))
            return 0.0
        }
    }
    if force == false {
        bind d = rt_status_data()
        # missing files are harmless to summon over; edits and offerings are not
        bind rs = list_get(d, 3.0)
        bind edits = 0.0
        bind q = 0.0
        while q < len(rs) {
            if list_get(rs, q) == "edit" { bind edits = edits + 1.0 }
            bind q = q + 1.0
        }
        bind dirty = len(list_get(d, 0.0)) + edits
        if dirty > 0.5 {
            print(cx_err("the temple is unsettled — offerings or restless spirits would be lost"))
            print(cx_dim("  seal a rite first, or summon with --force to discard"))
            return 0.0
        }
    }
    bind cur_m = sx_rite_manifest(sx_head_rite())
    bind new_m = sx_rite_manifest(rite)
    bind cids = list_get(cur_m, 0.0)
    bind cpaths = list_get(cur_m, 1.0)
    bind nids = list_get(new_m, 0.0)
    bind npaths = list_get(new_m, 1.0)
    bind wrote = 0.0
    bind i = 0.0
    while i < len(npaths) {
        bind p = list_get(npaths, i)
        bind at = ut_index_of(cpaths, p)
        bind need = true
        if at >= 0.0 {
            if list_get(cids, at) == list_get(nids, i) {
                if file_exists(sx_root() + "/" + p) { bind need = false }
            }
        }
        if need {
            sx_checkout_spirit(list_get(nids, i), sx_root() + "/" + p)
            bind wrote = wrote + 1.0
        }
        bind i = i + 1.0
    }
    bind gone = 0.0
    bind i = 0.0
    while i < len(cpaths) {
        bind p = list_get(cpaths, i)
        if ut_contains(npaths, p) == false {
            remove_file(sx_root() + "/" + p)
            bind gone = gone + 1.0
        }
        bind i = i + 1.0
    }
    sx_altar_write(list_new(), list_new())
    if is_soul {
        sx_set_head_soul(target)
        print(cx_good("✦ soul summoned: ") + cx_info(target) + cx_dim("  (" + wrote + " renewed, " + gone + " released)"))
    } else {
        sx_set_head_rite(rite)
        print(cx_warn("✦ wandering at rite ") + cx_aura(rite) + cx_dim("  (detached — kindle a soul with `miao soul <name>` to keep work)"))
    }
    sx_log("summon", target)
    return 0.0
}

# ── harmonize (merge) ─────────────────────────────────────────────────
fn rt_ancestors(id) {
    bind seen = list_new()
    bind queue = list_new()
    if id != "" { bind queue = list_push(queue, id) }
    bind qi = 0.0
    while qi < len(queue) {
        bind cur = list_get(queue, qi)
        if ut_contains(seen, cur) == false {
            bind seen = list_push(seen, cur)
            bind r = sx_rite_read(cur)
            bind ps = list_get(r, 1.0)
            bind k = 0.0
            while k < len(ps) {
                bind queue = list_push(queue, list_get(ps, k))
                bind k = k + 1.0
            }
        }
        bind qi = qi + 1.0
    }
    return seen
}

fn rt_merge_base(a, b) {
    bind anc = rt_ancestors(a)
    bind queue = list_new()
    bind queue = list_push(queue, b)
    bind seen = list_new()
    bind qi = 0.0
    while qi < len(queue) {
        bind cur = list_get(queue, qi)
        if ut_contains(anc, cur) { return cur }
        if ut_contains(seen, cur) == false {
            bind seen = list_push(seen, cur)
            bind r = sx_rite_read(cur)
            bind ps = list_get(r, 1.0)
            bind k = 0.0
            while k < len(ps) {
                bind queue = list_push(queue, list_get(ps, k))
                bind k = k + 1.0
            }
        }
        bind qi = qi + 1.0
    }
    return ""
}

fn rt_harmonize(args) {
    if len(args) < 1.0 {
        print(cx_err("harmonize with whom? try: miao harmonize <soul>"))
        return 0.0
    }
    bind name = list_get(args, 0.0)
    bind theirs = rt_resolve(name)
    if theirs == "" {
        print(cx_err("no soul, talisman or rite answers to: " + name))
        return 0.0
    }
    bind h = sx_head()
    if list_get(h, 0.0) != "soul" {
        print(cx_err("cannot harmonize while wandering detached — summon a soul first"))
        return 0.0
    }
    bind cur_soul = list_get(h, 1.0)
    bind ours = sx_head_rite()
    if theirs == ours {
        print(cx_good("already in perfect harmony"))
        return 0.0
    }
    bind d = rt_status_data()
    bind dirty = len(list_get(d, 0.0)) + len(list_get(d, 2.0))
    if dirty > 0.5 {
        print(cx_err("the temple is unsettled — seal or restore before harmonizing"))
        return 0.0
    }
    bind base = rt_merge_base(ours, theirs)
    if base == theirs {
        print(cx_good("the temple already holds everything from " + name))
        return 0.0
    }
    if base == ours || ours == "" {
        # fast-forward
        rt_summon_to_rite(theirs)
        sx_set_tip(cur_soul, theirs)
        sx_set_head_soul(cur_soul)
        print(cx_good("✦ fast-forward — ") + cx_info(cur_soul) + cx_good(" now flows at ") + cx_aura(theirs))
        sx_log("harmonize-ff", name)
        return 0.0
    }
    # true three-way merge
    bind mb = sx_rite_manifest(base)
    bind mo = sx_rite_manifest(ours)
    bind mt = sx_rite_manifest(theirs)
    bind union = list_new()
    bind src = [list_get(mb, 1.0), list_get(mo, 1.0), list_get(mt, 1.0)]
    bind s = 0.0
    while s < 3.0 {
        bind ps = list_get(src, s)
        bind i = 0.0
        while i < len(ps) {
            bind p = list_get(ps, i)
            if ut_contains(union, p) == false { bind union = list_push(union, p) }
            bind i = i + 1.0
        }
        bind s = s + 1.0
    }
    bind union = ut_sort(union)
    bind discord_paths = list_new()
    bind merged = 0.0
    bind i = 0.0
    while i < len(union) {
        bind p = list_get(union, i)
        bind ob = rt_m_id(mb, p)
        bind oo = rt_m_id(mo, p)
        bind ot = rt_m_id(mt, p)
        if oo == ot {
            # same on both sides: nothing to do
            bind i = i
        } else if ot == ob {
            # only ours changed: keep ours
            bind i = i
        } else if oo == ob {
            # only theirs changed: take theirs
            if ot == "" {
                if file_exists(sx_root() + "/" + p) { remove_file(sx_root() + "/" + p) }
                sx_altar_set(p, "-")
            } else {
                sx_checkout_spirit(ot, sx_root() + "/" + p)
                sx_altar_set(p, ot)
            }
            bind merged = merged + 1.0
        } else {
            # both sides changed
            bind can_text = true
            if oo == "" || ot == "" { bind can_text = false }
            if can_text {
                if rt_is_bin_spirit(oo) { bind can_text = false }
            }
            if can_text {
                if rt_is_bin_spirit(ot) { bind can_text = false }
            }
            if can_text {
                bind tb = ""
                if ob != "" { bind tb = rt_spirit_text_or(ob) }
                bind to2 = rt_spirit_text_or(oo)
                bind tt = rt_spirit_text_or(ot)
                bind mres = dy_merge3(tb, to2, tt, cur_soul, name)
                bind mtext = list_get(mres, 0.0)
                bind ncf = list_get(mres, 1.0)
                write_file(sx_root() + "/" + p, mtext)
                if ncf > 0.5 {
                    bind discord_paths = list_push(discord_paths, p)
                    print("  " + cx_err("☯ discord   " + p) + cx_dim("  (" + ncf + " clash(es) marked in the file)"))
                } else {
                    bind res = sx_spirit_put(sx_root() + "/" + p, oo)
                    sx_altar_set(p, list_get(res, 0.0))
                    print("  " + cx_good("☯ harmonized " + p))
                    bind merged = merged + 1.0
                }
            } else {
                # binary clash or add/delete clash: keep ours, flag discord
                bind discord_paths = list_push(discord_paths, p)
                print("  " + cx_err("☯ discord   " + p) + cx_dim("  (kept our spirit; offer the resolution)"))
            }
        }
        bind i = i + 1.0
    }
    if len(discord_paths) > 0.0 {
        bind t = "theirs|" + theirs + "|" + name + "\n"
        bind i = 0.0
        while i < len(discord_paths) {
            bind t = t + "path|" + list_get(discord_paths, i) + "\n"
            bind i = i + 1.0
        }
        write_file(sx_dir() + "/DISCORD", t)
        print(cx_err("" + len(discord_paths) + " spirit(s) in discord") + cx_dim(" — resolve, `miao offer` each, then seal with `miao rite -m \"...\"`"))
    } else {
        # record the second parent so the lineage knows this is a union
        write_file(sx_dir() + "/DISCORD", "theirs|" + theirs + "|" + name + "\n")
        rt_rite("Harmonize " + name + " into " + cur_soul)
    }
    return 0.0
}

fn rt_m_id(m, p) {
    bind at = ut_index_of(list_get(m, 1.0), p)
    if at < 0.0 { return "" }
    return list_get(list_get(m, 0.0), at)
}

# checkout helper used by fast-forward (no SOUL bookkeeping)
fn rt_summon_to_rite(rite) {
    bind cur_m = sx_rite_manifest(sx_head_rite())
    bind new_m = sx_rite_manifest(rite)
    bind cpaths = list_get(cur_m, 1.0)
    bind cids = list_get(cur_m, 0.0)
    bind npaths = list_get(new_m, 1.0)
    bind nids = list_get(new_m, 0.0)
    bind i = 0.0
    while i < len(npaths) {
        bind p = list_get(npaths, i)
        bind at = ut_index_of(cpaths, p)
        bind need = true
        if at >= 0.0 {
            if list_get(cids, at) == list_get(nids, i) {
                if file_exists(sx_root() + "/" + p) { bind need = false }
            }
        }
        if need { sx_checkout_spirit(list_get(nids, i), sx_root() + "/" + p) }
        bind i = i + 1.0
    }
    bind i = 0.0
    while i < len(cpaths) {
        bind p = list_get(cpaths, i)
        if ut_contains(npaths, p) == false {
            if file_exists(sx_root() + "/" + p) { remove_file(sx_root() + "/" + p) }
        }
        bind i = i + 1.0
    }
    return 0.0
}

# ── talismans (tags) ──────────────────────────────────────────────────
fn rt_talisman(args) {
    if len(args) < 1.0 {
        bind ts = sx_talismans()
        if len(ts) < 1.0 {
            print(cx_dim("no talismans hang in the temple"))
            return 0.0
        }
        bind i = 0.0
        while i < len(ts) {
            bind name = list_get(ts, i)
            bind id = sx_talisman_get(name)
            bind r = sx_rite_read(id)
            print(cx_tal("符 " + ut_pad(name, 16.0)) + " " + cx_aura(id) + " " + cx_dim(ut_first_line(list_get(r, 4.0))))
            bind i = i + 1.0
        }
        return 0.0
    }
    bind name = list_get(args, 0.0)
    bind at = sx_head_rite()
    if len(args) > 1.5 { bind at = rt_resolve(list_get(args, 1.0)) }
    if at == "" {
        print(cx_err("nothing to fix the talisman to"))
        return 0.0
    }
    sx_talisman_set(name, at)
    print(cx_tal("符 talisman hung: " + name) + cx_dim(" at ") + cx_aura(at))
    return 0.0
}

# ── manifest (show) ───────────────────────────────────────────────────
fn rt_manifest(args) {
    if len(args) < 1.0 {
        bind args = ["@"]
    }
    bind what = list_get(args, 0.0)
    if what == "@" { bind what = "" }
    bind rite = rt_resolve(what)
    if rite == "" {
        # maybe a path: show the enshrined content
        bind rel = rt_relpath(what)
        bind head = sx_rite_manifest(sx_head_rite())
        bind at = ut_index_of(list_get(head, 1.0), rel)
        if at >= 0.0 {
            bind id = list_get(list_get(head, 0.0), at)
            if rt_is_bin_spirit(id) {
                print(cx_warn("a binary spirit — " + rel))
            } else {
                print_file(sx_mat_text(id))
            }
            return 0.0
        }
        print(cx_err("nothing answers to: " + what))
        return 0.0
    }
    bind r = sx_rite_read(rite)
    bind m = sx_rite_manifest(rite)
    bind ids = list_get(m, 0.0)
    bind paths = list_get(m, 1.0)
    print(cx_rainbow("✦ rite ") + cx_aura(rite) + cx_dim("  (" + rite + ")"))
    print(cx_meta("  sealed   ") + cx_info(ut_fmt_time(list_get(r, 3.0))))
    print(cx_meta("  keeper   ") + cx_good(list_get(r, 2.0)))
    bind ps = list_get(r, 1.0)
    bind i = 0.0
    while i < len(ps) {
        print(cx_meta("  parent   ") + cx_aura(list_get(ps, i)))
        bind i = i + 1.0
    }
    print(cx_meta("  scripture") + cx_dim(" 「") + list_get(r, 4.0) + cx_dim("」"))
    print("")
    bind logical = 0.0
    bind i = 0.0
    while i < len(paths) {
        bind info = sx_spirit_info(list_get(ids, i))
        bind strat = list_get(info, 0.0)
        bind size = list_get(info, 3.0)
        bind logical = logical + size
        bind glyph = cx_good("○ whole ")
        if strat == "delta" { bind glyph = cx_info("◐ delta ") }
        if strat == "chunks" { bind glyph = cx_tal("◍ chunks") }
        print("  " + glyph + " " + ut_pad(list_get(paths, i), 40.0) + cx_dim(" " + size + " B"))
        bind i = i + 1.0
    }
    print(cx_dim("  " + len(paths) + " spirit(s), " + floor(logical / 1024.0) + " KiB enshrined"))
    return 0.0
}

# ── restore ───────────────────────────────────────────────────────────
fn rt_restore(args) {
    if len(args) < 1.0 {
        print(cx_err("restore what? try: miao restore <path>"))
        return 0.0
    }
    bind i = 0.0
    while i < len(args) {
        bind rel = rt_relpath(list_get(args, i))
        bind a = sx_altar()
        bind at = ut_index_of(list_get(a, 1.0), rel)
        bind id = ""
        if at >= 0.0 { bind id = list_get(list_get(a, 0.0), at) }
        if id == "" || id == "-" {
            bind head = sx_rite_manifest(sx_head_rite())
            bind hat = ut_index_of(list_get(head, 1.0), rel)
            if hat >= 0.0 { bind id = list_get(list_get(head, 0.0), hat) }
        }
        if id == "" || id == "-" {
            print("  " + cx_err("nothing enshrined at: " + rel))
        } else {
            sx_checkout_spirit(id, sx_root() + "/" + rel)
            print("  " + cx_good("↺ returned  ") + rel)
        }
        bind i = i + 1.0
    }
    return 0.0
}

← 索引に戻る