Skip to content

Latest commit

 

History

History
463 lines (387 loc) · 24.9 KB

File metadata and controls

463 lines (387 loc) · 24.9 KB

Issue #267/#269 索引组织迁移 + artifact 采纳 — 实施计划(0.0.103)

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 按设计文档 2026-07-22-issue267-269-index-artifact-and-org-migration-design.md 落地 D1–D5,单 PR 携版本 0.0.103,合入后走发布闭环 + xlings 全生态验证。

Architecture: 新/旧 URL 与 artifact base 收敛为 mcpp::config 三常量;canonicalize_legacy_index_names 导出并承担"URL 归一→名字迁移→artifact 内存级填充→去重"单循环;文件层治愈只做最小文本手术(config.toml URL 替换、.xlings.json URL 替换 + artifact 注入,幂等闸=已含 res base);seed 管线由 pair 升级为 SeedRepo{name,url,artifact,source},非空才发射,零 diff 门。

Tech Stack: C++23 modules(自托管 mcpp build/mcpp test)、gtest 单测(tests/unit)、bash e2e(tests/e2e)、gh CLI。

Global Constraints

  • 版本:mcpp.toml version 与 src/toolchain/fingerprint.cppm MCPP_VERSION 同 commit 同步0.0.103(不一致 release smoke 失败)。
  • 新 URL https://github.com/mcpplibs/mcpp-index.git;旧 URL https://github.com/mcpp-community/mcpp-index.git;artifact base https://github.com/xlings-res/mcpp-index
  • 无 artifact 的仓 seed 输出逐字节不变(零 diff 门)。
  • .xlings.json 迁移不整文件重生成(保 subos/版本绑定),只做目标文本替换,双间距变体(mcpp pretty / xlings compact)。
  • mcpp.fallback.config_migration 是叶子模块,不 import mcpp.config —— URL 字面量在此重复是接受的(与现有 name 迁移同风格)。
  • 构建/测试命令:mcpp build;MCPP_FRESH=$(realpath $(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2));"$MCPP_FRESH" test;e2e 单跑 MCPP="$MCPP_FRESH" bash tests/e2e/<n>.sh
  • 分支:feat/index-artifact-0.0.103 off origin/main(a768f48);单 PR;合并 gh pr merge --squash --admin

Task 0: 分支与文档

  • git checkout -b feat/index-artifact-0.0.103 origin/main
  • git add .agents/docs/2026-07-22-issue267-269-*.md && git commit -m "docs: index org migration + artifact adoption design & impl plan (#267 #269)"

Task 1: D1 — 常量 + canonicalize 导出与 URL 迁移(TDD)

Files:

  • Modify: src/config.cppm(导出区加三常量与 canonicalize_legacy_index_names 声明;原匿名 ns 实现移出;:360 模板 URL、:575 add_default URL)
  • Modify: src/fallback/config_migration.cppm(两迁移函数加 URL 替换)
  • Modify: src/publish/pipeline.cppm:158docs/00-getting-started.md:98docs/04-build-from-source.md:96docs/zh/00-getting-started.md:99docs/zh/04-build-from-source.md:103
  • Test: tests/unit/test_config.cpp

Interfaces (produces):

// mcpp::config, exported
inline constexpr std::string_view kMcpplibsIndexUrl       = "https://github.com/mcpplibs/mcpp-index.git";
inline constexpr std::string_view kMcpplibsIndexUrlLegacy = "https://github.com/mcpp-community/mcpp-index.git";
inline constexpr std::string_view kMcpplibsIndexArtifact  = "https://github.com/xlings-res/mcpp-index";
void canonicalize_legacy_index_names(GlobalConfig& cfg);   // 导出(可测性)
  • Step 1: 失败单测(test_config.cpp 末尾追加;import mcpp.fallback.config_migration; 加到文件头 import 区)
TEST(ConfigIndexMigration, CanonicalizeRewritesOrgAndNameAndFillsArtifact) {
    mcpp::config::GlobalConfig cfg;
    cfg.defaultIndex = "mcpp-index";
    cfg.indexRepos.push_back({ "mcpp-index",
        std::string(mcpp::config::kMcpplibsIndexUrlLegacy) });
    mcpp::config::canonicalize_legacy_index_names(cfg);
    ASSERT_EQ(cfg.indexRepos.size(), 1u);
    EXPECT_EQ(cfg.defaultIndex, "mcpplibs");
    EXPECT_EQ(cfg.indexRepos[0].name, "mcpplibs");
    EXPECT_EQ(cfg.indexRepos[0].url,  mcpp::config::kMcpplibsIndexUrl);
    EXPECT_EQ(cfg.indexRepos[0].artifact, mcpp::config::kMcpplibsIndexArtifact);
}

TEST(ConfigIndexMigration, CanonicalizeFoldsLegacyAndDefaultEntries) {
    mcpp::config::GlobalConfig cfg;
    cfg.indexRepos.push_back({ "mcpplibs", std::string(mcpp::config::kMcpplibsIndexUrlLegacy) });
    cfg.indexRepos.push_back({ "mcpplibs", std::string(mcpp::config::kMcpplibsIndexUrl) });
    mcpp::config::canonicalize_legacy_index_names(cfg);
    EXPECT_EQ(cfg.indexRepos.size(), 1u);
}

TEST(ConfigIndexMigration, CanonicalizeRespectsExplicitGitSourceAndForeignRepos) {
    mcpp::config::GlobalConfig cfg;
    mcpp::config::IndexRepo git{ "mcpplibs", std::string(mcpp::config::kMcpplibsIndexUrl) };
    git.source = "git";
    cfg.indexRepos.push_back(git);
    cfg.indexRepos.push_back({ "other", "https://example.com/other-index.git" });
    mcpp::config::canonicalize_legacy_index_names(cfg);
    EXPECT_TRUE(cfg.indexRepos[0].artifact.empty());
    EXPECT_TRUE(cfg.indexRepos[1].artifact.empty());
    EXPECT_EQ(cfg.indexRepos[1].url, "https://example.com/other-index.git");
}

TEST(ConfigIndexMigration, MigrateConfigTomlRewritesOrgUrlIdempotently) {
    auto dir = make_tempdir("mcpp-migrate-toml");
    auto p = dir / "config.toml";
    { std::ofstream os(p); os <<
        "[index]\ndefault = \"mcpplibs\"\n\n[index.repos.\"mcpplibs\"]\n"
        "url = \"https://github.com/mcpp-community/mcpp-index.git\"\n"; }
    EXPECT_TRUE(mcpp::fallback::migrate_config_toml_index_names(p));
    std::stringstream ss; ss << std::ifstream(p).rdbuf();
    EXPECT_NE(ss.str().find("github.com/mcpplibs/mcpp-index.git"), std::string::npos);
    EXPECT_EQ(ss.str().find("mcpp-community/mcpp-index"), std::string::npos);
    EXPECT_FALSE(mcpp::fallback::migrate_config_toml_index_names(p));  // 幂等
    std::filesystem::remove_all(dir);
}

注:IndexReposource/artifact 字段 Task 2 才加 —— 本任务与 Task 2 的实现同 commit 落(测试先全写,分两步跑)。若想严格分步,CanonicalizeRespectsExplicitGitSource 测试留到 Task 2。

  • Step 2: 跑测确认编译失败(常量/导出不存在):mcpp build 期望 FAIL
  • Step 3: 实现——config.cppm 导出区(struct IndexRepo 后)加三常量;把 canonicalize_legacy_index_names 从匿名 ns 移到导出区,实现:
void canonicalize_legacy_index_names(GlobalConfig& cfg) {
    if (cfg.defaultIndex == "mcpp-index") cfg.defaultIndex = "mcpplibs";
    std::vector<IndexRepo> normalized;
    for (auto r : cfg.indexRepos) {
        // ① 组织迁移(先于名字判断与去重:老配置匹配 + 新旧条目可折叠)
        if (r.url == kMcpplibsIndexUrlLegacy) r.url = std::string(kMcpplibsIndexUrl);
        // ② 名字迁移(URL 已归一,只认新 URL)
        if (r.name == "mcpp-index" && r.url == kMcpplibsIndexUrl) r.name = "mcpplibs";
        // ③ 内存级 artifact 填充(官方仓、未显式退出、未自定义)——Task 2 落地字段后启用
        if (r.url == kMcpplibsIndexUrl && r.artifact.empty() && r.source != "git")
            r.artifact = std::string(kMcpplibsIndexArtifact);
        bool duplicate = std::any_of(normalized.begin(), normalized.end(),
            [&](const IndexRepo& e) { return e.name == r.name && e.url == r.url; });
        if (!duplicate) normalized.push_back(std::move(r));
    }
    cfg.indexRepos = std::move(normalized);
}

config_migration.cppm 两函数各加(在现有 replace 之后):

replace_all(updated, "https://github.com/mcpp-community/mcpp-index.git",
                     "https://github.com/mcpplibs/mcpp-index.git");

模板 :360add_default :575 改用 kMcpplibsIndexUrl(模板是字符串字面量,直接写新 URL);publish/pipeline.cppm:158 Fork 行 → https://github.com/mcpplibs/mcpp-index;docs 4 处链接同改。

  • Step 4: mcpp build && "$MCPP_FRESH" test 期望 PASS(新测试含 Task 2 字段的暂缓)
  • Step 5: Commit fix(index): migrate mcpplibs index URL to mcpplibs org (#267-A)

Task 2: D2 — SeedRepo 管线 + IndexRepo/TOML/模板 artifact

Files:

  • Modify: src/config.cppm(IndexRepo 加字段:36;TOML 读 artifact/source:535-543;模板:359-361;add_default:571-575;write_default_xlings_json:374-396;print_env:663-668)
  • Modify: src/xlings.cppm(SeedRepo struct + seed_xlings_json 签名/发射:250-253、1110-1150)
  • Test: tests/unit/test_xlings.cpptests/unit/test_config.cpp

Interfaces (produces):

// mcpp::config
struct IndexRepo { std::string name, url, artifact, source; };
// mcpp::xlings
struct SeedRepo { std::string name, url, artifact, source; };
void seed_xlings_json(const Env&, std::span<const SeedRepo>,
                      std::string_view mirror = "auto", const ProjectEnv& = {});
  • Step 1: 失败单测(test_xlings.cpp,import mcpp.xlings; 已有则复用其 temp-dir 惯例)
TEST(XlingsSeed, PlainRepoEmissionIsByteStable) {   // 零 diff 门
    auto dir = make_tempdir("mcpp-seed-plain");
    mcpp::xlings::Env env; env.home = dir;
    std::vector<mcpp::xlings::SeedRepo> repos{ { "mcpplibs", "https://x.git", "", "" } };
    mcpp::xlings::seed_xlings_json(env, repos);
    std::stringstream ss; ss << std::ifstream(dir / ".xlings.json").rdbuf();
    EXPECT_NE(ss.str().find(
        "    { \"name\": \"mcpplibs\", \"url\": \"https://x.git\" }\n"), std::string::npos);
    EXPECT_EQ(ss.str().find("artifact"), std::string::npos);
    std::filesystem::remove_all(dir);
}

TEST(XlingsSeed, ArtifactAndSourceEmittedWhenSet) {
    auto dir = make_tempdir("mcpp-seed-art");
    mcpp::xlings::Env env; env.home = dir;
    std::vector<mcpp::xlings::SeedRepo> repos{
        { "mcpplibs", "https://x.git", "https://github.com/xlings-res/mcpp-index", "auto" } };
    mcpp::xlings::seed_xlings_json(env, repos);
    std::stringstream ss; ss << std::ifstream(dir / ".xlings.json").rdbuf();
    EXPECT_NE(ss.str().find("\"url\": \"https://x.git\", \"artifact\": "
        "\"https://github.com/xlings-res/mcpp-index\", \"source\": \"auto\""), std::string::npos);
    std::filesystem::remove_all(dir);
}
  • Step 2: 编译失败确认(SeedRepo 不存在)
  • Step 3: 实现——xlings.cppm 声明区加 struct SeedRepo { std::string name, url, artifact, source; };,签名改 std::span<const SeedRepo>;发射循环:
for (std::size_t i = 0; i < repos.size(); ++i) {
    json += std::format("    {{ \"name\": \"{}\", \"url\": \"{}\"",
                        json_escape(repos[i].name), json_escape(repos[i].url));
    if (!repos[i].artifact.empty())
        json += std::format(", \"artifact\": \"{}\"", json_escape(repos[i].artifact));
    if (!repos[i].source.empty())
        json += std::format(", \"source\": \"{}\"", json_escape(repos[i].source));
    json += std::format(" }}{}\n", i + 1 == repos.size() ? "" : ",");
}

config.cppm:IndexRepo 加 artifact/source;TOML 解析读两 key(可选 string);write_default_xlings_json pairs→std::vector<mcpp::xlings::SeedRepo> 直传四字段;模板加:

[index.repos."mcpplibs"]
url      = "https://github.com/mcpplibs/mcpp-index.git"
artifact = "https://github.com/xlings-res/mcpp-index"
# source = "auto"  # default: artifact first, git fallback; set "git" to force git

add_default lambda 加 artifact 形参并给 mcpplibs 传 kMcpplibsIndexArtifact;print_env 的 repo 行追加 [artifact] 标记(r.artifact 非空时);ensure_project_index_dircustomReposstd::vector<mcpp::xlings::SeedRepo>(artifact 字段 Task 4 接入,先留空串)。canonicalize 的 ③ 分支此时编译生效。

  • Step 4: mcpp build && "$MCPP_FRESH" test 全绿(含 Task 1 暂缓测试)
  • Step 5: Commit feat(index): per-repo artifact source plumbing — IndexRepo/SeedRepo/toml/template (#269)

Task 3: D3 — 存量 .xlings.json artifact 注入(TDD)

Files:

  • Modify: src/fallback/config_migration.cppm:69-81

  • Test: tests/unit/test_config.cpp

  • Step 1: 失败单测

namespace {
std::string read_all(const std::filesystem::path& p) {
    std::stringstream ss; ss << std::ifstream(p).rdbuf(); return ss.str();
}
}  // namespace

TEST(ConfigIndexMigration, XlingsJsonHealsPrettyLegacyEntry) {
    auto dir = make_tempdir("mcpp-migrate-xj");
    auto p = dir / ".xlings.json";
    { std::ofstream os(p); os << "{\n  \"index_repos\": [\n"
        "    { \"name\": \"mcpplibs\", \"url\": \"https://github.com/mcpp-community/mcpp-index.git\" }\n"
        "  ],\n  \"subos\": \"default\",\n  \"mirror\": \"auto\"\n}\n"; }
    EXPECT_TRUE(mcpp::fallback::migrate_xlings_json_index_names(p));
    auto text = read_all(p);
    EXPECT_NE(text.find("\"url\": \"https://github.com/mcpplibs/mcpp-index.git\", "
        "\"artifact\": \"https://github.com/xlings-res/mcpp-index\""), std::string::npos);
    EXPECT_NE(text.find("\"subos\": \"default\""), std::string::npos);  // 无关 key 保留
    EXPECT_EQ(text.find("mcpp-community/mcpp-index"), std::string::npos);
    EXPECT_FALSE(mcpp::fallback::migrate_xlings_json_index_names(p));   // 幂等
    std::filesystem::remove_all(dir);
}

TEST(ConfigIndexMigration, XlingsJsonHealsCompactLegacyNameAndUrl) {
    auto dir = make_tempdir("mcpp-migrate-xjc");
    auto p = dir / ".xlings.json";
    { std::ofstream os(p); os << "{\"index_repos\":[{\"name\":\"mcpp-index\","
        "\"url\":\"https://github.com/mcpp-community/mcpp-index.git\"}],\"mirror\":\"auto\"}"; }
    EXPECT_TRUE(mcpp::fallback::migrate_xlings_json_index_names(p));
    auto text = read_all(p);
    EXPECT_NE(text.find("\"name\":\"mcpplibs\""), std::string::npos);
    EXPECT_NE(text.find("\"url\":\"https://github.com/mcpplibs/mcpp-index.git\", "
        "\"artifact\": \"https://github.com/xlings-res/mcpp-index\""), std::string::npos);
    EXPECT_FALSE(mcpp::fallback::migrate_xlings_json_index_names(p));
    std::filesystem::remove_all(dir);
}

TEST(ConfigIndexMigration, XlingsJsonSkipsInjectionWhenArtifactPresent) {
    auto dir = make_tempdir("mcpp-migrate-xja");
    auto p = dir / ".xlings.json";
    std::string body = "{\n  \"index_repos\": [\n"
        "    { \"name\": \"mcpplibs\", \"url\": \"https://github.com/mcpplibs/mcpp-index.git\", "
        "\"artifact\": \"https://github.com/xlings-res/mcpp-index\" }\n  ]\n}\n";
    { std::ofstream os(p); os << body; }
    EXPECT_FALSE(mcpp::fallback::migrate_xlings_json_index_names(p));
    EXPECT_EQ(read_all(p), body);
    std::filesystem::remove_all(dir);
}
  • Step 2: 跑测确认 FAIL(artifact 未注入)
  • Step 3: 实现(接在 name/URL 替换后):
// Artifact injection (xlings 0.4.68 per-repo artifact source). Idempotency
// gate: the res base appearing anywhere means a previous run (or a fresh
// seed) already declared it — plain replace_all would re-inject on every run.
if (updated.find("xlings-res/mcpp-index") == std::string::npos) {
    constexpr std::string_view art =
        ", \"artifact\": \"https://github.com/xlings-res/mcpp-index\"";
    for (std::string_view urlkv : {
            "\"url\": \"https://github.com/mcpplibs/mcpp-index.git\"",
            "\"url\":\"https://github.com/mcpplibs/mcpp-index.git\"" })
        replace_all(updated, urlkv, std::string(urlkv) + std::string(art));
}
  • Step 4: mcpp build && "$MCPP_FRESH" test PASS
  • Step 5: Commit feat(index): heal existing registry .xlings.json — org URL + artifact injection (#267-A #269)

Task 4: D5 — 项目级 [indices] artifact/source + pin 交互

Files:

  • Modify: src/pm/index_spec.cppm:14-23(加字段 + artifact_applicable())
  • Modify: src/manifest/toml.cppm:1142-1150([indices] 长格式读 artifact/source)
  • Modify: src/config.cppm:546-563(config.toml [indices] 同步读)+ ensure_project_index_dir:680-691(SeedRepo 透传 + 不适用 warn)
  • Test: tests/unit/test_manifest.cpp

Interfaces (produces):

// mcpp::pm::IndexSpec 新增
std::string artifact;   // optional artifact source base (xlings 0.4.68+)
std::string source;     // optional "auto" | "artifact" | "git"
bool artifact_applicable() const {   // pin/local forces git — artifact only tracks latest
    return !artifact.empty() && rev.empty() && tag.empty() && branch.empty() && path.empty();
}
  • Step 1: 失败单测(test_manifest.cpp,仿现有 [indices] 解析测试写法;若无现成解析入口测试,则直接测 IndexSpec 语义 + toml 解析函数)
TEST(ManifestIndices, ArtifactAndSourceParsedAndPinSuppresses) {
    auto m = parse_manifest_from_string(R"(
[package]
name = "p"
version = "0.1.0"
[indices]
fast   = { url = "https://example.com/i.git", artifact = "https://example.com/res/i", source = "auto" }
pinned = { url = "https://example.com/i.git", artifact = "https://example.com/res/i", rev = "abc123" }
)");  // 按 test_manifest.cpp 现有 helper 名对齐
    auto& fast = m.indices.at("fast");
    EXPECT_EQ(fast.artifact, "https://example.com/res/i");
    EXPECT_EQ(fast.source, "auto");
    EXPECT_TRUE(fast.artifact_applicable());
    auto& pinned = m.indices.at("pinned");
    EXPECT_FALSE(pinned.artifact_applicable());
}
  • Step 2: FAIL 确认Step 3: 实现:两处解析各加两行 find("artifact")/find("source");ensure_project_index_dir 远程分支:
mcpp::xlings::SeedRepo r{ name, spec.url, "", "" };
if (spec.artifact_applicable()) { r.artifact = spec.artifact; r.source = spec.source; }
else if (!spec.artifact.empty())
    std::println("[warn] index '{}': artifact source ignored (rev/tag/branch/path pins force git)", name);
customRepos.push_back(std::move(r));
  • Step 4: 全测 PASSStep 5: Commit feat(index): project-level [indices] artifact/source with pin-forces-git rule (#269)

Task 5: read_seeded_index_repos 容错锁测

Files:

  • Test: tests/unit/test_pm_package_fetcher.cpp:237 附近克隆现有 fixture

  • Step 1: 新测——entry 含 artifact/source 仍正确提取 name/url:

TEST(PackageFetcher, SeededIndexReposTolerateArtifactFields) {
    auto project = make_tempdir("mcpp-pf-artifact");   // 对齐该文件现有 temp helper
    auto xjson = project / ".xlings.json";
    { std::ofstream os(xjson); os << "{\n  \"index_repos\": [\n"
        "    { \"name\": \"mcpplibs\", \"url\": \"https://github.com/mcpplibs/mcpp-index.git\", "
        "\"artifact\": \"https://github.com/xlings-res/mcpp-index\", \"source\": \"auto\" }\n  ]\n}\n"; }
    auto repos = mcpp::pm::read_seeded_index_repos(xjson);
    ASSERT_EQ(repos.size(), 1u);
    EXPECT_EQ(repos[0].first, "mcpplibs");
    EXPECT_EQ(repos[0].second, "https://github.com/mcpplibs/mcpp-index.git");
    std::filesystem::remove_all(project);
}

(返回类型按现有测试实际形态对齐 —— 以 :244 现有断言写法为准。)

  • Step 2: 跑测(预期直接 PASS —— 这是回归锁,不是新功能;若 FAIL 则修 read_seeded_index_repos 的对象切分)
  • Step 3: Commit test(pm): lock read_seeded_index_repos tolerance to artifact/source fields

Task 6: D4 — xlings pin → 0.4.68 + 常量对齐

Files:

  • Modify: .github/workflows/release.yml(:99:284:341-345 硬编码 0.4.67 全部)、.github/workflows/cross-build-test.yml:105,218.github/workflows/ci-linux-e2e.yml:126

  • Modify: src/xlings.cppm:36(kXlingsVersion "0.4.51"→"0.4.68")

  • Modify: src/config.cppm print_env(加 bundled xlings pin = {kXlingsPinnedVersion} 行,给常量一个消费点)

  • Step 1: grep -rn '0\.4\.67' .github/workflows/ 逐处替换为 0.4.68(含注释里的版本说明,注释补一句 0.4.68 = per-repo index artifact #377)

  • Step 2: 常量 + print_env;mcpp build && "$MCPP_FRESH" self env 目测新行

  • Step 3: Commit ci: bundled xlings pin 0.4.67 -> 0.4.68 (per-repo index artifact); align kXlingsVersion

Task 7: e2e — 151_index_url_artifact_migration.sh

Files:

  • Create: tests/e2e/151_index_url_artifact_migration.sh(仿 10_env_command.sh 的隔离 MCPP_HOME 手法;需 MCPP_VENDORED_XLINGS 或系统 xlings,CI bootstrap 已具备)

  • Step 1: 写脚本

#!/usr/bin/env bash
# #267/#269: fresh init seeds mcpplibs-org URL + artifact source; legacy
# config.toml/.xlings.json (old org URL, no artifact) are healed in place,
# idempotently, in both pretty (mcpp) and compact (xlings) JSON spacings.
set -e
TMP=$(mktemp -d); trap "rm -rf $TMP" EXIT
export MCPP_HOME="$TMP/mcpp-home"

NEW_URL='https://github.com/mcpplibs/mcpp-index.git'
OLD_URL='https://github.com/mcpp-community/mcpp-index.git'
ART='https://github.com/xlings-res/mcpp-index'
CFG="$MCPP_HOME/config.toml"; XJ="$MCPP_HOME/registry/.xlings.json"

# 1. Fresh init: new org URL + artifact in both files.
"$MCPP" self env > /dev/null
grep -q "url      = \"$NEW_URL\"" "$CFG"      || { echo "config.toml missing new url"; exit 1; }
grep -q "artifact = \"$ART\"" "$CFG"          || { echo "config.toml missing artifact"; exit 1; }
grep -q "\"url\": \"$NEW_URL\", \"artifact\": \"$ART\"" "$XJ" || { echo "seed missing artifact"; exit 1; }
grep -q 'mcpp-community/mcpp-index' "$CFG" "$XJ" && { echo "old org leaked"; exit 1; }

# 2. Legacy heal (pretty): old URL, no artifact.
cat > "$CFG" <<EOF
[xlings]
binary = "bundled"
[index]
default = "mcpplibs"
[index.repos."mcpplibs"]
url = "$OLD_URL"
EOF
cat > "$XJ" <<EOF
{
  "index_repos": [
    { "name": "mcpplibs", "url": "$OLD_URL" }
  ],
  "subos": "default",
  "lang": "en",
  "mirror": "auto"
}
EOF
"$MCPP" self env > /dev/null
grep -q "url = \"$NEW_URL\"" "$CFG"                            || { echo "toml url not healed"; exit 1; }
grep -q "\"url\": \"$NEW_URL\", \"artifact\": \"$ART\"" "$XJ"  || { echo "xj not healed"; exit 1; }
grep -q '"subos": "default"' "$XJ"                             || { echo "unrelated key lost"; exit 1; }
cp "$CFG" "$TMP/cfg1"; cp "$XJ" "$TMP/xj1"
"$MCPP" self env > /dev/null                                   # idempotent second run
cmp -s "$CFG" "$TMP/cfg1" && cmp -s "$XJ" "$TMP/xj1"           || { echo "not idempotent"; exit 1; }

# 3. Legacy heal (compact, old name): xlings-writer spacing.
printf '{"index_repos":[{"name":"mcpp-index","url":"%s"}],"mirror":"auto"}' "$OLD_URL" > "$XJ"
"$MCPP" self env > /dev/null
grep -q '"name":"mcpplibs"' "$XJ"        || { echo "compact name not healed"; exit 1; }
grep -q "xlings-res/mcpp-index" "$XJ"    || { echo "compact artifact not injected"; exit 1; }
grep -q 'mcpp-community' "$XJ" && { echo "compact old org left"; exit 1; }

echo "OK"
  • Step 2: 本地跑 MCPP="$MCPP_FRESH" bash tests/e2e/151_index_url_artifact_migration.sh → OK
  • Step 3: Commit test(e2e): 151 — index org URL heal + artifact seed/injection

Task 8: 版本 0.0.103 + CHANGELOG

  • mcpp.toml:3 version → 0.0.103;src/toolchain/fingerprint.cppm:210.0.103(同 commit!)
  • CHANGELOG.md 顶部加 0.0.103 条目(#267 URL 迁移 + 存量治愈、#269 artifact 采纳 + pin 0.4.68、[indices] artifact)
  • Commit release: 0.0.103 — index org migration + per-repo artifact adoption (#267 #269)

Task 9: 验证 + PR + CI + 合入

  • mcpp build && "$MCPP_FRESH" test 全绿;e2e 相关子集(10、151)绿;可选跑本机 e2e 基线抽样
  • git push -u origin feat/index-artifact-0.0.103;gh pr create(标题 feat(index): mcpplibs org migration + xlings 0.4.68 per-repo artifact adoption — 0.0.103 (#267 #269),body 引两 issue + 设计文档)
  • 等 CI 全绿(ci-linux、ci-linux-e2e 两 shard、cross-build-test、windows/macos e2e、fresh-install)
  • gh pr merge <n> --squash --admin(不加 --delete-branch 以防叠栈坑;单 PR 无叠栈,可加)

Task 10: 发布 + xlings 全生态验证(runbook = release-publish-pipeline 记忆 + .agents/skills/mcpp-release)

  • gh workflow run release.yml --ref main(从 mcpp.toml 派生 v0.0.103;publish-ecosystem 自动镜像 xlings-res/mcpp 双端 + 开 xim-pkgindex bump PR)
  • 双端 GET 核验四平台资产(curl -sL -o /dev/null -w '%{http_code} %{size_download}',GitHub + gitcode.com 直链主机,必须 200 + 真字节;HEAD 不可信)
  • gh pr merge <bump-pr> --repo openxlings/xim-pkgindex --squash --admin(publish-artifact.yml 自动发索引)
  • 实机验证:xlings update && xlings install mcpp@0.0.103 -ymcpp --version = 0.0.103
  • artifact 路径专项(本 PR 主功能):fresh MCPP_HOME + xlings ≥0.4.68 捆绑,mcpp index updateregistry/data/mcpplibs 变 artifact 管理(.git 消失、.xlings-index-version 出现);registry/.xlings.json mcpplibs 条目带 artifact 字段
  • 收尾 pin:工作区 .xlings.json bootstrap pin → 0.0.103 直推 main(ci: workspace mcpp bootstrap pin -> 0.0.103 (released + indexed));ci-fresh-install.yml 的 0.0.102 pin 同步 bump

Self-Review 结论

  • 设计文档 D1–D5 全覆盖(D1→T1,D2→T2,D3→T3,D5→T4,兼容锁→T5,D4→T6,e2e→T7);P5(CN region 对象)按设计文档明确推迟,非本计划范围。
  • 类型一致性:SeedRepo{name,url,artifact,source} 贯穿 T2/T4;IndexRepo 同形;artifact_applicable() 只在 T4 定义与使用。
  • 已知风险:test_manifest.cpp 的解析 helper 名需现场对齐(计划内已标注);ensure_project_index_dirspec.is_builtin() 分支(mcpplibs)不进 customRepos,不受 T4 影响。