def load(p):
    raw = open(p, encoding='utf-8', newline='').read()
    return raw.replace('\r\n', '\n'), '\r\n' in raw

def save(p, s, crlf):
    open(p, 'w', encoding='utf-8', newline='').write(s.replace('\n', '\r\n') if crlf else s)

def once(s, old, new):
    assert s.count(old) == 1, (old[:80], s.count(old))
    return s.replace(old, new)

p = 'crates/spt/src/main.rs'
s, c = load(p)
s = once(s, 'mod accessview;\n', 'mod accessview;\nmod adapterlist;\n')
save(p, s, c)

p = 'crates/spt/src/wansend.rs'
s, c = load(p)
anchor = '/// A verified adapter archive pulled from a peer, and who served it.'
s = once(s, anchor, '''/// Every adapter row every OTHER node publishes, as (peer display name, row),
/// read from the registry snapshots without dialing anyone — the `adapter
/// list` Available section (releases#264). One row per (node, adapter).
// [impl->REQ-ADAPTER-LIST-SURFACE]
pub fn subnet_adapter_rows_in(
    regs: &BTreeMap<String, SubnetRegistry>,
    own_hex: &str,
) -> Vec<(String, spt_net::net::update::AdapterRosterRow)> {
    let mut seen: Vec<(String, String)> = Vec::new();
    let mut out = Vec::new();
    for reg in regs.values() {
        let labels: BTreeMap<&str, &str> = reg.node_labels().collect();
        for (node, rows) in reg.node_adapters() {
            if node == own_hex {
                continue;
            }
            let label = labels
                .get(node)
                .map(|l| l.to_string())
                .or_else(|| label_of(node, regs))
                .unwrap_or_else(|| key_prefix(node));
            for row in rows {
                let key = (node.to_string(), row.name.clone());
                if !seen.contains(&key) {
                    seen.push(key);
                    out.push((label.clone(), row.clone()));
                }
            }
        }
    }
    out
}

/// [`subnet_adapter_rows_in`] over this node's own registry snapshots.
/// Read-only, like [`adapter_holders`].
// [impl->REQ-ADAPTER-LIST-SURFACE]
pub fn subnet_adapter_rows() -> Vec<(String, spt_net::net::update::AdapterRosterRow)> {
    let own_hex = spt_store::nodeid::load_existing()
        .map(|id| id.public_key().to_hex())
        .unwrap_or_default();
    subnet_adapter_rows_in(&load_snapshots(&perch::identity_dir().join("registry")), &own_hex)
}

''' + anchor)
save(p, s, c)

p = 'crates/spt/src/cli.rs'
s, c = load(p)
s = once(s, '''struct AdapterJson {
    name: String,
    kind: String,
    mode: String,
    version: Option<String>,
    source_dir: String,
    active: bool,
}''', '''struct AdapterJson {
    name: String,
    kind: String,
    mode: String,
    version: Option<String>,
    source_dir: String,
    active: bool,
    // Additive (releases#269): the human list's facts, for scripts.
    // [impl->REQ-ADAPTER-LIST-SURFACE]
    group: &'static str,
    built_in: bool,
    profiles: Vec<AdapterProfileJson>,
}

/// One profile under an adapter in `adapter list --json`.
// [impl->REQ-ADAPTER-LIST-SURFACE]
#[derive(serde::Serialize)]
struct AdapterProfileJson {
    name: String,
    custom: bool,
}

/// One Available-on-subnet row in `adapter list --json`.
// [impl->REQ-ADAPTER-LIST-SURFACE]
#[derive(serde::Serialize)]
struct AvailableAdapterJson {
    name: String,
    version: String,
    group: &'static str,
    peers: Vec<String>,
}''')
s = once(s, '''struct AdapterListJson {
    adapters: Vec<AdapterJson>,
}''', '''struct AdapterListJson {
    adapters: Vec<AdapterJson>,
    // [impl->REQ-ADAPTER-LIST-SURFACE]
    available: Vec<AvailableAdapterJson>,
}''')

old_start = '''        AdapterCmd::List => {
            let records = registry::all_records(&adapters);'''
old_end = '''                for p in registry::local_profile_names(&adapters, &r.name) {
                    println!("  {}:{p} (local)", r.name);
                }
            }
            0
        }'''
i = s.index(old_start)
j = s.index(old_end, i) + len(old_end)
assert s.count(old_start) == 1
new_arm = '''        AdapterCmd::List => {
            use crate::adapterlist;
            use spt_runtime::surfaces::adapter_group;
            let records = registry::all_records(&adapters);
            // One read per record: the manifest gives the version, the group
            // and the shipped profiles; overlays beside it are the custom ones.
            // [impl->REQ-MANIFEST-2]
            // [impl->REQ-ADAPTER-LIST-SURFACE]
            let rows: Vec<(registry::AdapterRecord, Option<spt_runtime::Manifest>, adapterlist::Installed)> = records
                .into_iter()
                .map(|r| {
                    let m = registry::load_manifest(&adapters, &r).ok();
                    let group = match &m {
                        Some(m) => adapter_group(m.adapter.kind, &m.adapter.hostable_types),
                        None => adapter_group(r.kind, &[]),
                    };
                    let mut profiles: Vec<adapterlist::Profile> = m
                        .as_ref()
                        .map(|m| m.profiles.keys().map(|p| adapterlist::Profile { name: p.clone(), custom: false }).collect())
                        .unwrap_or_default();
                    profiles.extend(
                        registry::local_profile_names(&adapters, &r.name)
                            .into_iter()
                            .map(|p| adapterlist::Profile { name: p, custom: true }),
                    );
                    let shown = adapterlist::Installed {
                        name: r.name.clone(),
                        version: m.as_ref().map(|m| m.adapter.version.clone()),
                        group,
                        built_in: r.built_in,
                        profiles,
                    };
                    (r, m, shown)
                })
                .collect();
            // A soft-deregistered adapter is not installed as far as a person
            // reading this list is concerned; --json still carries it.
            let installed: Vec<adapterlist::Installed> =
                rows.iter().filter(|(r, _, _)| r.active).map(|(_, _, i)| i.clone()).collect();
            let peer_rows: Vec<(String, String, String, spt_runtime::surfaces::AdapterGroup)> =
                crate::wansend::subnet_adapter_rows()
                    .into_iter()
                    .map(|(peer, row)| {
                        let kind = if row.kind == "shell" {
                            spt_runtime::manifest::AdapterKind::Shell
                        } else {
                            spt_runtime::manifest::AdapterKind::Harness
                        };
                        let group = adapter_group(kind, &row.hostable_types);
                        (peer, row.name, row.version, group)
                    })
                    .collect();
            let available = adapterlist::available(&installed, &peer_rows);
            // [impl->REQ-CLI-JSON]
            if json {
                let dtos = rows
                    .iter()
                    .map(|(r, m, shown)| AdapterJson {
                        name: r.name.clone(),
                        kind: format!("{:?}", r.kind),
                        mode: format!("{:?}", r.mode),
                        version: m.as_ref().map(|m| m.adapter.version.clone()),
                        source_dir: r.source_dir.clone(),
                        active: r.active,
                        group: adapterlist::group_word(shown.group),
                        built_in: r.built_in,
                        profiles: shown
                            .profiles
                            .iter()
                            .map(|p| AdapterProfileJson { name: p.name.clone(), custom: p.custom })
                            .collect(),
                    })
                    .collect();
                let available = available
                    .iter()
                    .map(|a| AvailableAdapterJson {
                        name: a.name.clone(),
                        version: a.version.clone(),
                        group: adapterlist::group_word(a.group),
                        peers: a.peers.clone(),
                    })
                    .collect();
                return print_json(&AdapterListJson { adapters: dtos, available });
            }
            print!("{}", adapterlist::render(&installed, &available, crate::helpfmt::stdout_color()));
            0
        }'''
s = s[:i] + new_arm + s[j:]
save(p, s, c)
print('ok')
