@uzysjung/agent-harness 26.166.0 → 26.168.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.ko.md +9 -9
  2. package/README.md +9 -9
  3. package/dist/{chunk-AHAAF4HS.js → chunk-V4UY222W.js} +151 -82
  4. package/dist/chunk-V4UY222W.js.map +1 -0
  5. package/dist/index.js +661 -373
  6. package/dist/index.js.map +1 -1
  7. package/dist/trust-tier-drift.js +6 -3
  8. package/dist/trust-tier-drift.js.map +1 -1
  9. package/package.json +1 -1
  10. package/templates/codex/config.toml.template +0 -3
  11. package/templates/github-workflows/ci-node.yml +1 -1
  12. package/templates/github-workflows/ci-python.yml +1 -1
  13. package/templates/github-workflows/e2e.yml +1 -1
  14. package/templates/mcp.json +1 -11
  15. package/templates/rules/ship-checklist.md +2 -2
  16. package/templates/rules/ui-components.md +13 -0
  17. package/templates/skills/audit-harness-fit/SKILL.md +15 -4
  18. package/templates/skills/audit-harness-fit/references/apply.md +18 -0
  19. package/templates/skills/audit-harness-fit/references/audit.md +16 -3
  20. package/templates/skills/audit-harness-fit/references/verification.md +7 -0
  21. package/templates/skills/chatgpt-delegate/SKILL.md +221 -0
  22. package/templates/skills/external-model-consult/SKILL.md +82 -40
  23. package/templates/skills/external-model-consult/references/direct-calls.md +23 -14
  24. package/templates/skills/external-model-consult/references/model-evidence.md +156 -0
  25. package/templates/skills/external-model-consult/scripts/codex-ask.sh +4 -3
  26. package/templates/skills/external-model-consult/scripts/gemini-ask.sh +3 -3
  27. package/templates/skills/gemini-delegate/SKILL.md +223 -0
  28. package/templates/skills/genspark-delegate/SKILL.md +283 -0
  29. package/templates/skills/gh-issue-workflow/SKILL.md +1 -1
  30. package/templates/skills/model-orchestration/SKILL.md +5 -0
  31. package/templates/skills/multi-model-debate/SKILL.md +187 -0
  32. package/templates/skills/multi-model-debate/agents/openai.yaml +4 -0
  33. package/templates/skills/multi-model-debate/references/adapters.md +143 -0
  34. package/templates/skills/multi-model-debate/references/evaluation.md +80 -0
  35. package/templates/skills/multi-model-debate/references/prompts.md +87 -0
  36. package/templates/skills/multi-model-debate/references/research.md +171 -0
  37. package/templates/track-mcp-map.tsv +10 -5
  38. package/dist/chunk-AHAAF4HS.js.map +0 -1
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  INTERNAL_BUNDLED_SKILL_IDS,
18
18
  LEGACY_INSTALL_LOG_DIR,
19
19
  POLICY_DIRS,
20
+ REMOVED_CATALOG_ASSETS,
20
21
  RENAMED_SKILL_IDS,
21
22
  RETIRED_AGENTS,
22
23
  RETIRED_AGENT_IDS,
@@ -102,7 +103,7 @@ import {
102
103
  targetsInclude,
103
104
  upsertHarnessImport,
104
105
  writeInstallLog
105
- } from "./chunk-AHAAF4HS.js";
106
+ } from "./chunk-V4UY222W.js";
106
107
 
107
108
  // node_modules/sisteransi/src/index.js
108
109
  var require_src = __commonJS({
@@ -787,7 +788,7 @@ var cac = (name = "") => new CAC(name);
787
788
  // src/commands/install.ts
788
789
  init_esm_shims();
789
790
  import { rmSync as rmSync4 } from "fs";
790
- import { join as join31, resolve as resolve2 } from "path";
791
+ import { join as join32, resolve as resolve2 } from "path";
791
792
  import { fileURLToPath } from "url";
792
793
 
793
794
  // src/adapters/index.ts
@@ -2992,8 +2993,8 @@ function padDisplay(s, width) {
2992
2993
 
2993
2994
  // src/install-writes.ts
2994
2995
  init_esm_shims();
2995
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
2996
- import { dirname, join as join5, relative, sep } from "path";
2996
+ import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2997
+ import { dirname, join as join6, relative, sep } from "path";
2997
2998
 
2998
2999
  // src/env-files.ts
2999
3000
  init_esm_shims();
@@ -3169,6 +3170,200 @@ function advisory(input) {
3169
3170
  return j("create", LINES.scaffoldWrote, "advisory");
3170
3171
  }
3171
3172
 
3173
+ // src/mcp-merge.ts
3174
+ init_esm_shims();
3175
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
3176
+ import { join as join5 } from "path";
3177
+
3178
+ // src/mcp-prior.ts
3179
+ init_esm_shims();
3180
+ var PRIOR_MCP_SERVERS = [
3181
+ {
3182
+ name: "github",
3183
+ value: { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-github"] },
3184
+ retired: "the old @modelcontextprotocol/server-github is deprecated upstream and ran without a token. The agent uses the gh CLI for GitHub; to use GitHub's official MCP instead: agent-harness install \u2026 --with github-mcp (needs GITHUB_PAT)"
3185
+ },
3186
+ {
3187
+ name: "railway-mcp-server",
3188
+ value: { type: "stdio", command: "npx", args: ["-y", "@railway/mcp-server"] },
3189
+ updated: "now Railway's remote server \u2014 sign in on first use, see NEXT"
3190
+ },
3191
+ {
3192
+ name: "chrome-devtools",
3193
+ value: { type: "stdio", command: "npx", args: ["-y", "chrome-devtools-mcp@latest"] },
3194
+ updated: "usage statistics off"
3195
+ }
3196
+ ];
3197
+ function priorMcpValues(name) {
3198
+ return PRIOR_MCP_SERVERS.filter((p2) => p2.name === name).map((p2) => p2.value);
3199
+ }
3200
+ function retiredMcpNames() {
3201
+ return PRIOR_MCP_SERVERS.filter((p2) => p2.retired !== void 0).map((p2) => p2.name);
3202
+ }
3203
+
3204
+ // src/mcp-merge.ts
3205
+ var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
3206
+ function parseRemote(raw) {
3207
+ let v2;
3208
+ try {
3209
+ v2 = JSON.parse(raw);
3210
+ } catch {
3211
+ return null;
3212
+ }
3213
+ if (v2 === null || typeof v2 !== "object" || Array.isArray(v2)) return null;
3214
+ const { url, bearerEnv } = v2;
3215
+ if (typeof url !== "string" || !url.startsWith("https://")) return null;
3216
+ if (bearerEnv === void 0) return { type: "http", url };
3217
+ if (typeof bearerEnv !== "string" || !ENV_NAME.test(bearerEnv)) return null;
3218
+ return { type: "http", url, auth: { bearerEnv } };
3219
+ }
3220
+ function parseTrackMcpMap(raw) {
3221
+ const rows = [];
3222
+ for (const line of raw.split(/\r?\n/)) {
3223
+ const trimmed = line.trim();
3224
+ if (!trimmed || trimmed.startsWith("#")) {
3225
+ continue;
3226
+ }
3227
+ const parts = line.split(" ");
3228
+ if (parts.length < 4) {
3229
+ continue;
3230
+ }
3231
+ const [name, pattern, command, argsJson] = parts;
3232
+ if (!name || !pattern || !command) {
3233
+ continue;
3234
+ }
3235
+ if (command === "url") {
3236
+ const server = parseRemote(argsJson ?? "");
3237
+ if (server !== null) rows.push({ name, pattern, server });
3238
+ continue;
3239
+ }
3240
+ let args;
3241
+ try {
3242
+ args = JSON.parse(argsJson ?? "[]");
3243
+ } catch {
3244
+ continue;
3245
+ }
3246
+ if (!Array.isArray(args) || !args.every((a) => typeof a === "string")) {
3247
+ continue;
3248
+ }
3249
+ rows.push({ name, pattern, server: { type: "stdio", command, args } });
3250
+ }
3251
+ return rows;
3252
+ }
3253
+ function toClaudeServer(cfg) {
3254
+ if (cfg.type === "http") {
3255
+ return cfg.auth === void 0 ? { type: "http", url: cfg.url } : {
3256
+ type: "http",
3257
+ url: cfg.url,
3258
+ headers: { Authorization: `Bearer \${${cfg.auth.bearerEnv}}` }
3259
+ };
3260
+ }
3261
+ const out = {
3262
+ type: cfg.type ?? "stdio",
3263
+ command: cfg.command,
3264
+ args: cfg.args
3265
+ };
3266
+ if (cfg.env && Object.keys(cfg.env).length > 0) out.env = { ...cfg.env };
3267
+ return out;
3268
+ }
3269
+ function mergeMcpServers(base, rows, tracks, chosen = [], dropped = []) {
3270
+ const out = {
3271
+ ...base,
3272
+ mcpServers: { ...base.mcpServers }
3273
+ };
3274
+ for (const row of rows) {
3275
+ if (dropped.includes(row.name)) {
3276
+ continue;
3277
+ }
3278
+ if (!anyTrack(tracks, row.pattern) && !chosen.includes(row.name)) {
3279
+ continue;
3280
+ }
3281
+ if (out.mcpServers[row.name]) {
3282
+ continue;
3283
+ }
3284
+ out.mcpServers[row.name] = row.server;
3285
+ }
3286
+ delete out._comment;
3287
+ return out;
3288
+ }
3289
+ function composeMcpJson(opts) {
3290
+ const base = JSON.parse(readFileSync3(opts.templateMcpPath, "utf8"));
3291
+ const mapRaw = existsSync4(opts.trackMapPath) ? readFileSync3(opts.trackMapPath, "utf8") : "";
3292
+ const rows = parseTrackMcpMap(mapRaw);
3293
+ return mergeMcpServers(base, rows, opts.tracks, opts.chosen(rows), opts.dropped?.(rows) ?? []);
3294
+ }
3295
+ function selectableMcpServers(rows) {
3296
+ const names = new Set(rows.map((r) => r.name));
3297
+ const out = /* @__PURE__ */ new Map();
3298
+ for (const a of EXTERNAL_ASSETS) {
3299
+ if (a.method.kind === "internal" && names.has(a.method.key)) out.set(a.method.key, a.id);
3300
+ }
3301
+ return out;
3302
+ }
3303
+ function selectedMcpServers(rows, choice) {
3304
+ const recorded2 = new Set(
3305
+ (choice.previousLog?.portions ?? []).filter((p2) => p2.path === ".mcp.json").map((p2) => p2.key)
3306
+ );
3307
+ const legacy = new Set(
3308
+ choice.projectDir === void 0 ? [] : legacyMcpServers(choice.projectDir, choice.previousLog, rows)
3309
+ );
3310
+ const dropped = new Set(droppedMcpServers(rows, choice.excluded));
3311
+ const out = [];
3312
+ for (const [name, assetId] of selectableMcpServers(rows)) {
3313
+ if (dropped.has(name)) continue;
3314
+ const key = `mcpServers.${name}`;
3315
+ if (isAssetSelected(assetId, choice.spec) || recorded2.has(key) || legacy.has(name)) {
3316
+ out.push(name);
3317
+ }
3318
+ }
3319
+ return out;
3320
+ }
3321
+ function legacyMcpServers(projectDir, previousLog, rows) {
3322
+ if (previousLog === null || previousLog.records === "writer") return [];
3323
+ if ((previousLog.portions ?? []).some((p2) => p2.path === ".mcp.json")) return [];
3324
+ const created = (previousLog.rootFiles ?? []).some(
3325
+ (f) => f.path === ".mcp.json" && f.change === "created"
3326
+ );
3327
+ if (!created) return [];
3328
+ const servers = readMcpServers(join5(projectDir, ".mcp.json"));
3329
+ if (servers === null) return [];
3330
+ const out = [];
3331
+ for (const name of selectableMcpServers(rows).keys()) {
3332
+ if (!Object.hasOwn(servers, name)) continue;
3333
+ const disk = jsonSha(servers[name]);
3334
+ const row = rows.find((r) => r.name === name);
3335
+ const known = [...row ? [row.server] : [], ...priorMcpValues(name)];
3336
+ if (known.some((v2) => jsonSha(toClaudeServer(v2)) === disk)) out.push(name);
3337
+ }
3338
+ return out;
3339
+ }
3340
+ function readMcpServers(path) {
3341
+ if (!existsSync4(path)) return null;
3342
+ try {
3343
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
3344
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
3345
+ const servers = parsed.mcpServers;
3346
+ if (servers === null || typeof servers !== "object" || Array.isArray(servers)) return null;
3347
+ return servers;
3348
+ } catch {
3349
+ return null;
3350
+ }
3351
+ }
3352
+ function droppedMcpServers(rows, excluded) {
3353
+ return [...selectableMcpServers(rows)].filter(([name, assetId]) => {
3354
+ const id = keyId(".mcp.json", `mcpServers.${name}`);
3355
+ return excluded.has(assetId) || id !== null && excluded.has(id);
3356
+ }).map(([name]) => name);
3357
+ }
3358
+ function recordedMcpAssetIds(log) {
3359
+ const recorded2 = new Set(
3360
+ (log?.portions ?? []).filter((p2) => p2.path === ".mcp.json").map((p2) => p2.key)
3361
+ );
3362
+ return EXTERNAL_ASSETS.filter(
3363
+ (a) => a.method.kind === "internal" && recorded2.has(`mcpServers.${a.method.key}`)
3364
+ ).map((a) => a.id);
3365
+ }
3366
+
3172
3367
  // src/recorded.ts
3173
3368
  init_esm_shims();
3174
3369
 
@@ -3325,10 +3520,10 @@ function createInstallWriter(args) {
3325
3520
  else external.set(path, sha256);
3326
3521
  }
3327
3522
  function harness(path, from, opts = {}) {
3328
- const abs = join5(projectDir, path);
3523
+ const abs = join6(projectDir, path);
3329
3524
  if (outside.skip(abs)) return null;
3330
- const next = "source" in from ? readFileSync3(from.source, "utf8") : from.content;
3331
- const disk = existsSync4(abs) ? readFileSync3(abs, "utf8") : null;
3525
+ const next = "source" in from ? readFileSync4(from.source, "utf8") : from.content;
3526
+ const disk = existsSync5(abs) ? readFileSync4(abs, "utf8") : null;
3332
3527
  const j4 = judge({
3333
3528
  op: "write",
3334
3529
  kind: "harness",
@@ -3390,8 +3585,8 @@ function createInstallWriter(args) {
3390
3585
  const file = SHARED_FILES[path];
3391
3586
  if (file === void 0) throw new Error(`install: ${path} is not a shared file`);
3392
3587
  const adapter = ADAPTERS[file.adapter];
3393
- const abs = join5(projectDir, path);
3394
- const disk = existsSync4(abs) ? readFileSync3(abs, "utf8") : null;
3588
+ const abs = join6(projectDir, path);
3589
+ const disk = existsSync5(abs) ? readFileSync4(abs, "utf8") : null;
3395
3590
  const own = new Map(
3396
3591
  (previousLog?.portions ?? []).filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
3397
3592
  );
@@ -3471,8 +3666,10 @@ function createInstallWriter(args) {
3471
3666
  const wrote = onlyRemoved ? "removed the harness part" : j4.line;
3472
3667
  const line = res.changed ? removedEdited.length > 0 ? wrote.replace(" \u2014 yours stays", "") : wrote : valueKeys2.length > 0 ? "kept \u2014 the harness part is already in place" : "nothing written \u2014 yours already has these";
3473
3668
  const retired = names(res.removed.filter((k2) => !out.has(k2) && !render.has(k2)));
3669
+ const replaced = names(res.replaced);
3474
3670
  return result(j4.verdict, line, {
3475
3671
  ...retired.length > 0 ? { retired } : {},
3672
+ ...replaced.length > 0 ? { replaced } : {},
3476
3673
  changed: res.changed,
3477
3674
  harness: names(valueKeys2),
3478
3675
  added: names(addedKeys),
@@ -3655,7 +3852,7 @@ function legacySettingsSeed(text, render, projectDir) {
3655
3852
  } catch {
3656
3853
  return seed;
3657
3854
  }
3658
- const claudeDir = join5(projectDir, ".claude");
3855
+ const claudeDir = join6(projectDir, ".claude");
3659
3856
  const candidates = new Set([...render.keys()].filter((k2) => k2.startsWith("hooks.")));
3660
3857
  const hooks = isObject2(root) && isObject2(root.hooks) ? root.hooks : {};
3661
3858
  for (const [event, groups] of Object.entries(hooks)) {
@@ -3685,8 +3882,14 @@ function legacyMcpSeed(text, render, previous) {
3685
3882
  (f) => f.path === ".mcp.json" && f.change === "created"
3686
3883
  );
3687
3884
  if (!created) return seed;
3688
- const present2 = ADAPTERS["json-keys"].read(text, render.keys()) ?? /* @__PURE__ */ new Map();
3689
- for (const key of present2.keys()) seed.set(key, jsonSha(render.get(key)));
3885
+ const retired = retiredMcpNames().map((n) => `mcpServers.${n}`);
3886
+ const candidates = [...render.keys(), ...retired.filter((k2) => !render.has(k2))];
3887
+ const present2 = ADAPTERS["json-keys"].read(text, candidates) ?? /* @__PURE__ */ new Map();
3888
+ for (const [key, disk] of present2) {
3889
+ const prior = priorMcpValues(key.slice("mcpServers.".length)).map((v2) => jsonSha(toClaudeServer(v2))).find((sha) => sha === disk);
3890
+ if (render.has(key)) seed.set(key, prior ?? jsonSha(render.get(key)));
3891
+ else if (prior !== void 0) seed.set(key, prior);
3892
+ }
3690
3893
  seed.set("mcpServers{}", "legacy");
3691
3894
  return seed;
3692
3895
  }
@@ -3701,16 +3904,39 @@ function writeSettingsShared(writer, templateText, projectDir, previousLog, hook
3701
3904
  createdNote: "Claude Code \uC124\uC815 \u2014 \uD558\uB124\uC2A4 \uBAAB\uB9CC(\uD6C5 \xB7 statusLine)"
3702
3905
  });
3703
3906
  }
3704
- function writeMcpShared(writer, mcp, previousLog, opts = {}) {
3907
+ function writeMcpShared(writer, mcp, previousLog, opts = {}, defaults) {
3705
3908
  const servers = mcp.mcpServers;
3706
3909
  const render = new Map(
3707
- Object.entries(servers).map(([name, cfg]) => [`mcpServers.${name}`, cfg])
3910
+ Object.entries(servers).map(([name, cfg]) => [`mcpServers.${name}`, toClaudeServer(cfg)])
3708
3911
  );
3709
- return writer.shared(".mcp.json", render, {
3912
+ const res = writer.shared(".mcp.json", render, {
3710
3913
  ...opts,
3711
3914
  legacySeed: (text) => legacyMcpSeed(text, render, previousLog),
3712
3915
  createdNote: "MCP \uC11C\uBC84 \uC815\uC758 \uC0DD\uC131"
3713
3916
  });
3917
+ const inFile = new Set(res.harness);
3918
+ const replaced = res.replaced ?? [];
3919
+ const notes = {};
3920
+ for (const p2 of PRIOR_MCP_SERVERS) {
3921
+ if (p2.retired !== void 0 && (res.retired ?? []).includes(p2.name)) notes[p2.name] = p2.retired;
3922
+ if (p2.updated !== void 0 && replaced.includes(p2.name)) notes[p2.name] = p2.updated;
3923
+ }
3924
+ if (defaults !== void 0) {
3925
+ for (const name of replaced) {
3926
+ const assetId = EXTERNAL_ASSETS.find(
3927
+ (a) => a.method.kind === "internal" && a.method.key === name
3928
+ )?.id;
3929
+ if (assetId === void 0 || defaults.names.has(name)) continue;
3930
+ const why = `not a default on ${defaults.tracks.join(", ")} \u2014 kept because your install record has it; drop it: agent-harness install \u2026 --without ${assetId}`;
3931
+ notes[name] = notes[name] === void 0 ? why : `${notes[name]}; ${why}`;
3932
+ }
3933
+ }
3934
+ const remote = Object.entries(servers).flatMap(
3935
+ ([name, cfg]) => cfg.type === "http" && inFile.has(name) ? [cfg.auth === void 0 ? { name } : { name, bearerEnv: cfg.auth.bearerEnv }] : []
3936
+ );
3937
+ if (Object.keys(notes).length > 0) res.notes = notes;
3938
+ if (remote.length > 0) res.remote = remote;
3939
+ return res;
3714
3940
  }
3715
3941
  function writeGitignoreShared(writer, previousLog) {
3716
3942
  const render = gitignoreRender();
@@ -3740,12 +3966,12 @@ function legacyGitignoreSeed(text, render, previous) {
3740
3966
  // src/installer.ts
3741
3967
  init_esm_shims();
3742
3968
  import { chmodSync as chmodSync3, existsSync as existsSync21, readdirSync as readdirSync4, readFileSync as readFileSync22, writeFileSync as writeFileSync8 } from "fs";
3743
- import { basename as basename4, join as join28, relative as relative3, resolve, sep as sep4 } from "path";
3969
+ import { basename as basename4, join as join29, relative as relative3, resolve, sep as sep4 } from "path";
3744
3970
 
3745
3971
  // src/anchor-seed.ts
3746
3972
  init_esm_shims();
3747
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
3748
- import { join as join6 } from "path";
3973
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
3974
+ import { join as join7 } from "path";
3749
3975
 
3750
3976
  // src/agents-md-render.ts
3751
3977
  init_esm_shims();
@@ -3829,9 +4055,9 @@ ${CODEX_SIZE_NOTE}` : RULES_NOTE : "",
3829
4055
 
3830
4056
  // src/anchor-seed.ts
3831
4057
  function readIfExists(path) {
3832
- if (!existsSync5(path)) return null;
4058
+ if (!existsSync6(path)) return null;
3833
4059
  try {
3834
- return readFileSync4(path, "utf8");
4060
+ return readFileSync5(path, "utf8");
3835
4061
  } catch {
3836
4062
  return null;
3837
4063
  }
@@ -3852,7 +4078,7 @@ function compose(body, into) {
3852
4078
  ${trimmed.join("\n")}`;
3853
4079
  }
3854
4080
  function seedAgentsMdProjectContext(projectDir) {
3855
- const root = readIfExists(join6(projectDir, "CLAUDE.md"));
4081
+ const root = readIfExists(join7(projectDir, "CLAUDE.md"));
3856
4082
  if (root === null) return null;
3857
4083
  const withoutImport = stripHarnessImport(root) ?? root;
3858
4084
  const body = dropHarnessQuoteBlocks(dropTitle(withoutImport.split("\n")));
@@ -3860,9 +4086,9 @@ function seedAgentsMdProjectContext(projectDir) {
3860
4086
  return compose(body, "agents-md");
3861
4087
  }
3862
4088
  function seedRootClaudeProjectContext(projectDir, harnessRoot) {
3863
- const agents = readIfExists(join6(projectDir, "AGENTS.md"));
4089
+ const agents = readIfExists(join7(projectDir, "AGENTS.md"));
3864
4090
  if (agents === null) return null;
3865
- const template = readIfExists(join6(harnessRoot, AGENTS_MD_TEMPLATE));
4091
+ const template = readIfExists(join7(harnessRoot, AGENTS_MD_TEMPLATE));
3866
4092
  if (template === null) return null;
3867
4093
  const installer = projectContextInstallerLines({ existing: agents, template });
3868
4094
  if (installer === null) return null;
@@ -3873,8 +4099,8 @@ function seedRootClaudeProjectContext(projectDir, harnessRoot) {
3873
4099
 
3874
4100
  // src/ci-scaffold.ts
3875
4101
  init_esm_shims();
3876
- import { existsSync as existsSync6 } from "fs";
3877
- import { join as join7 } from "path";
4102
+ import { existsSync as existsSync7 } from "fs";
4103
+ import { join as join8 } from "path";
3878
4104
  var PYTHON_TRACKS = "data|csr-fastapi|full";
3879
4105
  var VARIANTS = [
3880
4106
  {
@@ -3898,12 +4124,12 @@ function installCiScaffold(ctx) {
3898
4124
  for (const v2 of VARIANTS) {
3899
4125
  if (!v2.applies(ctx.tracks)) continue;
3900
4126
  const relTarget = `.github/workflows/${v2.target}`;
3901
- const target = join7(ctx.projectDir, relTarget);
3902
- if (existsSync6(target)) {
4127
+ const target = join8(ctx.projectDir, relTarget);
4128
+ if (existsSync7(target)) {
3903
4129
  report.skippedExisting.push(relTarget);
3904
4130
  continue;
3905
4131
  }
3906
- copyFile(join7(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
4132
+ copyFile(join8(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
3907
4133
  report.written.push(relTarget);
3908
4134
  }
3909
4135
  return report;
@@ -3912,16 +4138,16 @@ function installCiScaffold(ctx) {
3912
4138
  // src/cli-transforms.ts
3913
4139
  init_esm_shims();
3914
4140
  import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
3915
- import { join as join20 } from "path";
4141
+ import { join as join21 } from "path";
3916
4142
 
3917
4143
  // src/agents-md-transform.ts
3918
4144
  init_esm_shims();
3919
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
3920
- import { basename, join as join12 } from "path";
4145
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
4146
+ import { basename, join as join13 } from "path";
3921
4147
 
3922
4148
  // src/agents-skill-targets.ts
3923
4149
  init_esm_shims();
3924
- import { join as join8, sep as sep2 } from "path";
4150
+ import { join as join9, sep as sep2 } from "path";
3925
4151
  function bundledSkillTargets(projectDir) {
3926
4152
  const log = readInstallLog(projectDir);
3927
4153
  if (log === null) return [];
@@ -3936,7 +4162,7 @@ function bundledSkillTargets(projectDir) {
3936
4162
  }
3937
4163
  function allowSkillCreation(writer, projectDir, ids) {
3938
4164
  if (ids.length === 0) return writer;
3939
- const roots = ids.map((id) => `${join8(projectDir, ".agents", "skills", id)}${sep2}`);
4165
+ const roots = ids.map((id) => `${join9(projectDir, ".agents", "skills", id)}${sep2}`);
3940
4166
  return {
3941
4167
  write(absPath, content, opts) {
3942
4168
  const inTarget = roots.some((root) => absPath.startsWith(root));
@@ -3957,24 +4183,24 @@ function agentsSkillSlot(params) {
3957
4183
 
3958
4184
  // src/owned-write.ts
3959
4185
  init_esm_shims();
3960
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
4186
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3961
4187
  import { dirname as dirname2, relative as relative2, sep as sep3 } from "path";
3962
4188
 
3963
4189
  // src/foreign-slot.ts
3964
4190
  init_esm_shims();
3965
4191
  import { lstatSync, realpathSync, statSync } from "fs";
3966
- import { join as join9 } from "path";
4192
+ import { join as join10 } from "path";
3967
4193
  var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
3968
4194
  function occupiedByNonDirectory(path) {
3969
4195
  const stat = lstatSync(path, { throwIfNoEntry: false });
3970
4196
  return stat !== void 0 && !stat.isDirectory();
3971
4197
  }
3972
4198
  function linksToProjectSharedSkill(projectDir, id) {
3973
- const slot = join9(projectDir, ".claude/skills", id);
4199
+ const slot = join10(projectDir, ".claude/skills", id);
3974
4200
  if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
3975
4201
  try {
3976
4202
  const real = realpathSync(slot);
3977
- return real === join9(realpathSync(projectDir), ".agents/skills", id) && statSync(real).isDirectory();
4203
+ return real === join10(realpathSync(projectDir), ".agents/skills", id) && statSync(real).isDirectory();
3978
4204
  } catch {
3979
4205
  return false;
3980
4206
  }
@@ -3982,15 +4208,15 @@ function linksToProjectSharedSkill(projectDir, id) {
3982
4208
  function foreignOwnedTarget(projectDir, rel) {
3983
4209
  const slot = SKILL_SLOT.exec(rel)?.[1];
3984
4210
  if (slot === void 0) return null;
3985
- if (occupiedByNonDirectory(join9(projectDir, slot))) return slot;
4211
+ if (occupiedByNonDirectory(join10(projectDir, slot))) return slot;
3986
4212
  if (rel !== slot) {
3987
4213
  const parts = rel.slice(slot.length + 1).split("/");
3988
4214
  let cursor = slot;
3989
4215
  for (const part of parts.slice(0, -1)) {
3990
4216
  cursor = `${cursor}/${part}`;
3991
- if (occupiedByNonDirectory(join9(projectDir, cursor))) return cursor;
4217
+ if (occupiedByNonDirectory(join10(projectDir, cursor))) return cursor;
3992
4218
  }
3993
- const leaf = lstatSync(join9(projectDir, rel), { throwIfNoEntry: false });
4219
+ const leaf = lstatSync(join10(projectDir, rel), { throwIfNoEntry: false });
3994
4220
  if (leaf !== void 0 && !leaf.isFile()) return rel;
3995
4221
  }
3996
4222
  return null;
@@ -4016,7 +4242,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
4016
4242
  }
4017
4243
  if (outside.skip(absPath)) return false;
4018
4244
  const digest = hashContent(content);
4019
- if (!existsSync7(absPath)) {
4245
+ if (!existsSync8(absPath)) {
4020
4246
  const recordedPath = baseline.has(rel);
4021
4247
  if (refreshOnly && !opts?.createInRefresh && !recordedPath) return false;
4022
4248
  mkdirSync2(dirname2(absPath), { recursive: true });
@@ -4024,7 +4250,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
4024
4250
  updated++;
4025
4251
  if (refreshOnly && recordedPath) restored.push(rel);
4026
4252
  } else {
4027
- const current = readFileSync5(absPath, "utf8");
4253
+ const current = readFileSync6(absPath, "utf8");
4028
4254
  if (current !== content) {
4029
4255
  if (!isHarnessOwned(baseline, rel, current)) {
4030
4256
  const backup = backupFile(absPath, now);
@@ -4058,8 +4284,8 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
4058
4284
 
4059
4285
  // src/rules-port.ts
4060
4286
  init_esm_shims();
4061
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync6 } from "fs";
4062
- import { join as join10 } from "path";
4287
+ import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync7 } from "fs";
4288
+ import { join as join11 } from "path";
4063
4289
  function claudePaths(source) {
4064
4290
  if (!source.startsWith("---\n")) return [];
4065
4291
  const end = source.indexOf("\n---", 4);
@@ -4087,16 +4313,16 @@ function stripClaudeFrontmatter(source) {
4087
4313
  function portRules(harnessRoot, ruleNames) {
4088
4314
  const ported = [];
4089
4315
  for (const name of ruleNames) {
4090
- const src = join10(harnessRoot, "templates/rules", `${name}.md`);
4091
- if (!existsSync8(src)) continue;
4092
- const source = readFileSync6(src, "utf8");
4316
+ const src = join11(harnessRoot, "templates/rules", `${name}.md`);
4317
+ if (!existsSync9(src)) continue;
4318
+ const source = readFileSync7(src, "utf8");
4093
4319
  ported.push({ name, body: stripClaudeFrontmatter(source), paths: claudePaths(source) });
4094
4320
  }
4095
4321
  return ported;
4096
4322
  }
4097
4323
  function ruleHeadings(harnessRoot) {
4098
- const dir = join10(harnessRoot, "templates/rules");
4099
- if (!existsSync8(dir)) return /* @__PURE__ */ new Set();
4324
+ const dir = join11(harnessRoot, "templates/rules");
4325
+ if (!existsSync9(dir)) return /* @__PURE__ */ new Set();
4100
4326
  const names = readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3));
4101
4327
  const block = renderRulesBlock(portRules(harnessRoot, names));
4102
4328
  return new Set(
@@ -4110,8 +4336,8 @@ function renderRulesBlock(rules) {
4110
4336
 
4111
4337
  // src/shared-write.ts
4112
4338
  init_esm_shims();
4113
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
4114
- import { dirname as dirname3, join as join11 } from "path";
4339
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
4340
+ import { dirname as dirname3, join as join12 } from "path";
4115
4341
  function toPortions(path, portions) {
4116
4342
  const adapter = adapterFor(path);
4117
4343
  if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
@@ -4126,8 +4352,8 @@ function writeShared(params) {
4126
4352
  const { projectDir, path, render, record, baseline, writer, refreshOnly } = params;
4127
4353
  const adapter = adapterFor(path);
4128
4354
  if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
4129
- const abs = join11(projectDir, path);
4130
- const onDisk = existsSync9(abs) ? readFileSync7(abs, "utf8") : null;
4355
+ const abs = join12(projectDir, path);
4356
+ const onDisk = existsSync10(abs) ? readFileSync8(abs, "utf8") : null;
4131
4357
  const recorded2 = recordedFor(path, record);
4132
4358
  const excluded = excludedKeys(path, record.excluded ?? []);
4133
4359
  const result = (action2, rest = {}) => ({
@@ -4246,8 +4472,8 @@ function agentsMdModel(disk, recordedByHarness, recordedBlock = false) {
4246
4472
  return "block";
4247
4473
  }
4248
4474
  function writeAgentsMd(params) {
4249
- const abs = join11(params.projectDir, AGENTS_MD);
4250
- const disk = existsSync9(abs) ? readFileSync7(abs, "utf8") : null;
4475
+ const abs = join12(params.projectDir, AGENTS_MD);
4476
+ const disk = existsSync10(abs) ? readFileSync8(abs, "utf8") : null;
4251
4477
  const recordedBlock = (params.record.portions ?? []).some((p2) => p2.path === AGENTS_MD);
4252
4478
  if (agentsMdModel(disk, params.baseline.has(AGENTS_MD), recordedBlock) === "sections") {
4253
4479
  params.writer.write(
@@ -4300,9 +4526,9 @@ function stripShared(params) {
4300
4526
  portions: [],
4301
4527
  ...rest
4302
4528
  });
4303
- const abs = join11(projectDir, path);
4304
- if (!existsSync9(abs) || !statSync2(abs).isFile()) return out("none");
4305
- const disk = readFileSync7(abs, "utf8");
4529
+ const abs = join12(projectDir, path);
4530
+ if (!existsSync10(abs) || !statSync2(abs).isFile()) return out("none");
4531
+ const disk = readFileSync8(abs, "utf8");
4306
4532
  const recorded2 = new Map(
4307
4533
  params.portions.filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
4308
4534
  );
@@ -4355,16 +4581,16 @@ function runAgentsMdTransform(params) {
4355
4581
  refreshOnly,
4356
4582
  ...params.journal === void 0 ? {} : { journal: params.journal }
4357
4583
  });
4358
- const claudeMd = readRequired(join12(harnessRoot, "templates/CLAUDE.md"));
4359
- const template = readRequired(join12(harnessRoot, AGENTS_MD_TEMPLATE));
4584
+ const claudeMd = readRequired(join13(harnessRoot, "templates/CLAUDE.md"));
4585
+ const template = readRequired(join13(harnessRoot, AGENTS_MD_TEMPLATE));
4360
4586
  const { skillIds } = agentsSkillSlot({
4361
4587
  projectDir,
4362
4588
  selectedInternalSkills: params.selectedInternalSkills,
4363
4589
  refreshOnly,
4364
4590
  writer
4365
4591
  });
4366
- const agentsMdPath = join12(projectDir, "AGENTS.md");
4367
- const exists = existsSync10(agentsMdPath);
4592
+ const agentsMdPath = join13(projectDir, "AGENTS.md");
4593
+ const exists = existsSync11(agentsMdPath);
4368
4594
  const creates = !exists && (!refreshOnly || params.installedByRecord === true);
4369
4595
  const legacy = params.legacyContext ?? null;
4370
4596
  const fromRoot = creates ? seedAgentsMdProjectContext(projectDir) : null;
@@ -4383,7 +4609,7 @@ function runAgentsMdTransform(params) {
4383
4609
  harnessRules: renderRulesBlock(portRules(harnessRoot, params.rules)),
4384
4610
  clis
4385
4611
  });
4386
- const diskBefore = exists ? readFileSync8(agentsMdPath, "utf8") : null;
4612
+ const diskBefore = exists ? readFileSync9(agentsMdPath, "utf8") : null;
4387
4613
  const agentsMd = writeAgentsMd({
4388
4614
  projectDir,
4389
4615
  rendered,
@@ -4406,22 +4632,22 @@ function runAgentsMdTransform(params) {
4406
4632
  };
4407
4633
  }
4408
4634
  function seededInto(path, before, seed, from) {
4409
- if (seed === null || !existsSync10(path)) return null;
4410
- const after = readFileSync8(path, "utf8");
4635
+ if (seed === null || !existsSync11(path)) return null;
4636
+ const after = readFileSync9(path, "utf8");
4411
4637
  const body = renameSlashes(seed.slice(seed.indexOf("\n\n") + 2));
4412
4638
  return after !== before && after.includes(body) ? from : null;
4413
4639
  }
4414
4640
  function readRequired(path) {
4415
- if (!existsSync10(path)) {
4641
+ if (!existsSync11(path)) {
4416
4642
  throw new Error(`AGENTS.md transform: required source missing: ${path}`);
4417
4643
  }
4418
- return readFileSync8(path, "utf8");
4644
+ return readFileSync9(path, "utf8");
4419
4645
  }
4420
4646
 
4421
4647
  // src/antigravity/retire-anchor.ts
4422
4648
  init_esm_shims();
4423
- import { lstatSync as lstatSync2, readFileSync as readFileSync9, rmSync as rmSync2 } from "fs";
4424
- import { join as join13 } from "path";
4649
+ import { lstatSync as lstatSync2, readFileSync as readFileSync10, rmSync as rmSync2 } from "fs";
4650
+ import { join as join14 } from "path";
4425
4651
  var LEGACY_ANTIGRAVITY_ANCHOR_SECTIONS = ["Project Context", "Project Rules", "Protected Files"];
4426
4652
  function anchorRecord(previous) {
4427
4653
  return previous.find((f) => f.path === LEGACY_ANTIGRAVITY_ANCHOR) ?? null;
@@ -4430,7 +4656,7 @@ function readRegular(abs) {
4430
4656
  const stat = lstatSync2(abs, { throwIfNoEntry: false });
4431
4657
  if (stat === void 0 || !stat.isFile()) return null;
4432
4658
  try {
4433
- return readFileSync9(abs, "utf8");
4659
+ return readFileSync10(abs, "utf8");
4434
4660
  } catch {
4435
4661
  return null;
4436
4662
  }
@@ -4449,7 +4675,7 @@ function legacyContextLines(text) {
4449
4675
  }
4450
4676
  function readLegacyAntigravityContext(projectDir, previous) {
4451
4677
  if (anchorRecord(previous) === null) return null;
4452
- const text = readRegular(join13(projectDir, LEGACY_ANTIGRAVITY_ANCHOR));
4678
+ const text = readRegular(join14(projectDir, LEGACY_ANTIGRAVITY_ANCHOR));
4453
4679
  if (text === null) return null;
4454
4680
  const lines2 = legacyContextLines(text);
4455
4681
  if (lines2 === null) return null;
@@ -4473,7 +4699,7 @@ function retireAntigravityAnchor(args) {
4473
4699
  const rec = anchorRecord(args.previous);
4474
4700
  if (rec === null) return null;
4475
4701
  const path = LEGACY_ANTIGRAVITY_ANCHOR;
4476
- const abs = join13(args.projectDir, path);
4702
+ const abs = join14(args.projectDir, path);
4477
4703
  const left = (why) => ({
4478
4704
  path,
4479
4705
  action: "left",
@@ -4484,7 +4710,7 @@ function retireAntigravityAnchor(args) {
4484
4710
  if (args.outside.skip(abs)) return left("it points outside this project");
4485
4711
  const stat = lstatSync2(abs, { throwIfNoEntry: false });
4486
4712
  if (stat !== void 0 && !stat.isFile()) return left("it is a link or folder, so it is yours");
4487
- const disk = stat === void 0 ? null : readFileSync9(abs, "utf8");
4713
+ const disk = stat === void 0 ? null : readFileSync10(abs, "utf8");
4488
4714
  const verdict = judge({
4489
4715
  op: "remove",
4490
4716
  kind: "harness",
@@ -4511,12 +4737,12 @@ function relativeBackup(projectDir, backup) {
4511
4737
 
4512
4738
  // src/antigravity/transform.ts
4513
4739
  init_esm_shims();
4514
- import { join as join15 } from "path";
4740
+ import { join as join16 } from "path";
4515
4741
 
4516
4742
  // src/codex/skills.ts
4517
4743
  init_esm_shims();
4518
- import { chmodSync, existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
4519
- import { join as join14 } from "path";
4744
+ import { chmodSync, existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
4745
+ import { join as join15 } from "path";
4520
4746
  function renderBundledSkill(source) {
4521
4747
  const trimmed = source.trimEnd();
4522
4748
  const lines2 = trimmed.split(/\r?\n/);
@@ -4548,14 +4774,14 @@ function writeBundledSkillDirs(params) {
4548
4774
  const { harnessRoot, projectDir, skillIds, writer } = params;
4549
4775
  const written = [];
4550
4776
  for (const id of skillIds) {
4551
- const srcDir = join14(harnessRoot, "templates/skills", id);
4552
- const srcSkill = join14(srcDir, "SKILL.md");
4553
- if (!existsSync11(srcSkill)) {
4777
+ const srcDir = join15(harnessRoot, "templates/skills", id);
4778
+ const srcSkill = join15(srcDir, "SKILL.md");
4779
+ if (!existsSync12(srcSkill)) {
4554
4780
  continue;
4555
4781
  }
4556
- const targetDir = join14(projectDir, ".agents", "skills", id);
4557
- const targetSkill = join14(targetDir, "SKILL.md");
4558
- if (!writer.write(targetSkill, renderBundledSkill(readFileSync10(srcSkill, "utf8")))) {
4782
+ const targetDir = join15(projectDir, ".agents", "skills", id);
4783
+ const targetSkill = join15(targetDir, "SKILL.md");
4784
+ if (!writer.write(targetSkill, renderBundledSkill(readFileSync11(srcSkill, "utf8")))) {
4559
4785
  continue;
4560
4786
  }
4561
4787
  written.push(targetSkill);
@@ -4563,10 +4789,10 @@ function writeBundledSkillDirs(params) {
4563
4789
  if (rel === "SKILL.md") continue;
4564
4790
  if (rel.split("/").some((segment2) => segment2.startsWith("."))) continue;
4565
4791
  const segments = rel.split("/");
4566
- const raw = readFileSync10(join14(srcDir, ...segments), "utf8");
4792
+ const raw = readFileSync11(join15(srcDir, ...segments), "utf8");
4567
4793
  const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
4568
4794
  ` : raw;
4569
- const target = join14(targetDir, ...segments);
4795
+ const target = join15(targetDir, ...segments);
4570
4796
  if (!writer.write(target, ported, { createInRefresh: true })) continue;
4571
4797
  if (rel.endsWith(".sh")) chmodSync(target, 493);
4572
4798
  written.push(target);
@@ -4607,7 +4833,7 @@ function runAntigravityTransform(params) {
4607
4833
  const antigravityInstalled = params.installedByRecord === true;
4608
4834
  const harnessRuleFiles = [];
4609
4835
  for (const rule of portRules(harnessRoot, rules)) {
4610
- const target = join15(projectDir, ".agents", "rules", `${rule.name}.md`);
4836
+ const target = join16(projectDir, ".agents", "rules", `${rule.name}.md`);
4611
4837
  const content = `${antigravityFrontmatter(rule)}${rule.body}
4612
4838
  `;
4613
4839
  if (!writer.write(target, content, { createInRefresh: antigravityInstalled })) {
@@ -4631,11 +4857,11 @@ function runAntigravityTransform(params) {
4631
4857
  // src/codex/opt-in.ts
4632
4858
  init_esm_shims();
4633
4859
  import { homedir } from "os";
4634
- import { join as join16 } from "path";
4860
+ import { join as join17 } from "path";
4635
4861
 
4636
4862
  // src/codex/trust-entry.ts
4637
4863
  init_esm_shims();
4638
- import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync11, realpathSync as realpathSync2, writeFileSync as writeFileSync6 } from "fs";
4864
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync12, realpathSync as realpathSync2, writeFileSync as writeFileSync6 } from "fs";
4639
4865
  import { dirname as dirname4 } from "path";
4640
4866
  var UNSAFE_APPEND = "adding the entry would make this file unreadable TOML \u2014 left untouched";
4641
4867
  function trustKeysFor(projectDir) {
@@ -4678,7 +4904,7 @@ function registerTrustEntry(opts) {
4678
4904
  const keys = trustKeysFor(projectDir);
4679
4905
  const key = keys[0] ?? projectDir;
4680
4906
  try {
4681
- const existing = existsSync12(configPath) ? readFileSync11(configPath, "utf8") : "";
4907
+ const existing = existsSync13(configPath) ? readFileSync12(configPath, "utf8") : "";
4682
4908
  let parsed;
4683
4909
  try {
4684
4910
  parsed = parse(existing);
@@ -4729,8 +4955,8 @@ function firstLine(e2) {
4729
4955
 
4730
4956
  // src/codex/opt-in.ts
4731
4957
  function runCodexOptIn(ctx) {
4732
- const codexHome = ctx.codexHome ?? join16(homedir(), ".codex");
4733
- const configPath = join16(codexHome, "config.toml");
4958
+ const codexHome = ctx.codexHome ?? join17(homedir(), ".codex");
4959
+ const configPath = join17(codexHome, "config.toml");
4734
4960
  const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
4735
4961
  return {
4736
4962
  trustEntry: {
@@ -4745,13 +4971,13 @@ function runCodexOptIn(ctx) {
4745
4971
 
4746
4972
  // src/codex/transform.ts
4747
4973
  init_esm_shims();
4748
- import { chmodSync as chmodSync2, existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
4749
- import { basename as basename2, join as join18 } from "path";
4974
+ import { chmodSync as chmodSync2, existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
4975
+ import { basename as basename2, join as join19 } from "path";
4750
4976
 
4751
4977
  // src/codex/config-toml.ts
4752
4978
  init_esm_shims();
4753
- import { readFileSync as readFileSync12 } from "fs";
4754
- import { join as join17 } from "path";
4979
+ import { readFileSync as readFileSync13 } from "fs";
4980
+ import { join as join18 } from "path";
4755
4981
  var TABLES_REGION = "tables";
4756
4982
  function configRegions(rendered) {
4757
4983
  const lines2 = rendered.split(/\r?\n/);
@@ -4796,7 +5022,7 @@ function retiredFeatureKeys(disk) {
4796
5022
  function retiredFeaturesLeftBehind(projectDir, r) {
4797
5023
  if (r.action !== "left" && !r.leftAsIs.includes(TABLES_REGION)) return [];
4798
5024
  try {
4799
- return retiredFeatureKeys(readFileSync12(join17(projectDir, ".codex/config.toml"), "utf8"));
5025
+ return retiredFeatureKeys(readFileSync13(join18(projectDir, ".codex/config.toml"), "utf8"));
4800
5026
  } catch {
4801
5027
  return [];
4802
5028
  }
@@ -4814,7 +5040,7 @@ function isChangedLegacyConfig(disk, baselineSha) {
4814
5040
  return parsed !== null && callsHarnessHook(parsed);
4815
5041
  }
4816
5042
  function renderConfigToml(params) {
4817
- const substituted = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_DIR}", tomlBasicString(params.projectDir).slice(1, -1)).replaceAll("{GITHUB_TOKEN}", "${GITHUB_TOKEN}");
5043
+ const substituted = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_DIR}", tomlBasicString(params.projectDir).slice(1, -1));
4818
5044
  if (!params.mcp) {
4819
5045
  return substituted;
4820
5046
  }
@@ -4826,6 +5052,12 @@ ${renderMcpServers(params.mcp)}
4826
5052
  function renderMcpServers(mcp) {
4827
5053
  const blocks = Object.entries(mcp.mcpServers).map(([name, cfg]) => {
4828
5054
  const lines2 = [`[mcp_servers.${quoteIfNeeded(name)}]`];
5055
+ if (cfg.type === "http") {
5056
+ lines2.push(`url = ${jsonString(cfg.url)}`);
5057
+ if (cfg.auth !== void 0)
5058
+ lines2.push(`bearer_token_env_var = ${jsonString(cfg.auth.bearerEnv)}`);
5059
+ return lines2.join("\n");
5060
+ }
4829
5061
  lines2.push(`command = ${jsonString(cfg.command)}`);
4830
5062
  lines2.push(`args = ${JSON.stringify(cfg.args)}`);
4831
5063
  if (cfg.env && Object.keys(cfg.env).length > 0) {
@@ -4860,7 +5092,7 @@ function runCodexTransform(params) {
4860
5092
  refreshOnly: refreshOnly ?? false,
4861
5093
  ...params.journal === void 0 ? {} : { journal: params.journal }
4862
5094
  });
4863
- const configTemplate = readRequired2(join18(harnessRoot, "templates/codex/config.toml.template"));
5095
+ const configTemplate = readRequired2(join19(harnessRoot, "templates/codex/config.toml.template"));
4864
5096
  const projectName = basename2(projectDir);
4865
5097
  const { skillIds, skillWriter } = agentsSkillSlot({
4866
5098
  projectDir,
@@ -4868,7 +5100,7 @@ function runCodexTransform(params) {
4868
5100
  refreshOnly: refreshOnly ?? false,
4869
5101
  writer
4870
5102
  });
4871
- const configTomlPath = join18(projectDir, CONFIG_TOML);
5103
+ const configTomlPath = join19(projectDir, CONFIG_TOML);
4872
5104
  const configToml = writeConfigToml({
4873
5105
  projectDir,
4874
5106
  regions: configRegions(
@@ -4879,15 +5111,15 @@ function runCodexTransform(params) {
4879
5111
  record: shared2,
4880
5112
  refreshOnly: refreshOnly ?? false
4881
5113
  });
4882
- const hookDir = join18(projectDir, ".codex/hooks");
5114
+ const hookDir = join19(projectDir, ".codex/hooks");
4883
5115
  const hookFiles = [];
4884
5116
  for (const hook of HOOK_NAMES) {
4885
- const src = join18(harnessRoot, "templates/hooks", `${hook}.sh`);
4886
- if (!existsSync13(src)) {
5117
+ const src = join19(harnessRoot, "templates/hooks", `${hook}.sh`);
5118
+ if (!existsSync14(src)) {
4887
5119
  continue;
4888
5120
  }
4889
- const ported = readFileSync13(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
4890
- const target = join18(hookDir, `${hook}.sh`);
5121
+ const ported = readFileSync14(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
5122
+ const target = join19(hookDir, `${hook}.sh`);
4891
5123
  if (!writer.write(target, ported)) continue;
4892
5124
  chmodSync2(target, 493);
4893
5125
  hookFiles.push(target);
@@ -4909,8 +5141,8 @@ function runCodexTransform(params) {
4909
5141
  var CONFIG_TOML = ".codex/config.toml";
4910
5142
  var LEGACY_CHANGED = "written by an earlier harness version and changed since \u2014 left as is; harness part not added";
4911
5143
  function writeConfigToml(args) {
4912
- const abs = join18(args.projectDir, CONFIG_TOML);
4913
- const disk = existsSync13(abs) ? readFileSync13(abs, "utf8") : null;
5144
+ const abs = join19(args.projectDir, CONFIG_TOML);
5145
+ const disk = existsSync14(abs) ? readFileSync14(abs, "utf8") : null;
4914
5146
  const regions = disk === null ? null : ADAPTERS["toml-region"].read(disk);
4915
5147
  if (disk !== null && regions?.size === 0 && isChangedLegacyConfig(disk, args.baseline.get(CONFIG_TOML))) {
4916
5148
  return {
@@ -4940,131 +5172,42 @@ function writeConfigToml(args) {
4940
5172
  });
4941
5173
  }
4942
5174
  function readRequired2(path) {
4943
- if (!existsSync13(path)) {
5175
+ if (!existsSync14(path)) {
4944
5176
  throw new Error(`Codex transform: required source missing: ${path}`);
4945
5177
  }
4946
- return readFileSync13(path, "utf8");
4947
- }
4948
-
4949
- // src/mcp-merge.ts
4950
- init_esm_shims();
4951
- import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
4952
- function parseTrackMcpMap(raw) {
4953
- const rows = [];
4954
- for (const line of raw.split(/\r?\n/)) {
4955
- const trimmed = line.trim();
4956
- if (!trimmed || trimmed.startsWith("#")) {
4957
- continue;
4958
- }
4959
- const parts = line.split(" ");
4960
- if (parts.length < 4) {
4961
- continue;
4962
- }
4963
- const [name, pattern, command, argsJson] = parts;
4964
- if (!name || !pattern || !command) {
4965
- continue;
4966
- }
4967
- let args;
4968
- try {
4969
- args = JSON.parse(argsJson ?? "[]");
4970
- } catch {
4971
- continue;
4972
- }
4973
- if (!Array.isArray(args) || !args.every((a) => typeof a === "string")) {
4974
- continue;
4975
- }
4976
- rows.push({ name, pattern, command, args });
4977
- }
4978
- return rows;
4979
- }
4980
- function mergeMcpServers(base, rows, tracks, chosen = [], dropped = []) {
4981
- const out = {
4982
- ...base,
4983
- mcpServers: { ...base.mcpServers }
4984
- };
4985
- for (const row of rows) {
4986
- if (dropped.includes(row.name)) {
4987
- continue;
4988
- }
4989
- if (!anyTrack(tracks, row.pattern) && !chosen.includes(row.name)) {
4990
- continue;
4991
- }
4992
- if (out.mcpServers[row.name]) {
4993
- continue;
4994
- }
4995
- out.mcpServers[row.name] = {
4996
- type: "stdio",
4997
- command: row.command,
4998
- args: row.args
4999
- };
5000
- }
5001
- delete out._comment;
5002
- return out;
5003
- }
5004
- function composeMcpJson(opts) {
5005
- const base = JSON.parse(readFileSync14(opts.templateMcpPath, "utf8"));
5006
- const mapRaw = existsSync14(opts.trackMapPath) ? readFileSync14(opts.trackMapPath, "utf8") : "";
5007
- const rows = parseTrackMcpMap(mapRaw);
5008
- return mergeMcpServers(base, rows, opts.tracks, opts.chosen(rows), opts.dropped?.(rows) ?? []);
5009
- }
5010
- function selectableMcpServers(rows) {
5011
- const names = new Set(rows.map((r) => r.name));
5012
- const out = /* @__PURE__ */ new Map();
5013
- for (const a of EXTERNAL_ASSETS) {
5014
- if (a.method.kind === "internal" && names.has(a.method.key)) out.set(a.method.key, a.id);
5015
- }
5016
- return out;
5017
- }
5018
- function selectedMcpServers(rows, choice) {
5019
- const recorded2 = new Set(
5020
- (choice.previousLog?.portions ?? []).filter((p2) => p2.path === ".mcp.json").map((p2) => p2.key)
5021
- );
5022
- const dropped = new Set(droppedMcpServers(rows, choice.excluded));
5023
- const out = [];
5024
- for (const [name, assetId] of selectableMcpServers(rows)) {
5025
- if (dropped.has(name)) continue;
5026
- const key = `mcpServers.${name}`;
5027
- if (isAssetSelected(assetId, choice.spec) || recorded2.has(key)) out.push(name);
5028
- }
5029
- return out;
5030
- }
5031
- function droppedMcpServers(rows, excluded) {
5032
- return [...selectableMcpServers(rows)].filter(([name, assetId]) => {
5033
- const id = keyId(".mcp.json", `mcpServers.${name}`);
5034
- return excluded.has(assetId) || id !== null && excluded.has(id);
5035
- }).map(([name]) => name);
5036
- }
5037
- function recordedMcpAssetIds(log) {
5038
- const recorded2 = new Set(
5039
- (log?.portions ?? []).filter((p2) => p2.path === ".mcp.json").map((p2) => p2.key)
5040
- );
5041
- return EXTERNAL_ASSETS.filter(
5042
- (a) => a.method.kind === "internal" && recorded2.has(`mcpServers.${a.method.key}`)
5043
- ).map((a) => a.id);
5178
+ return readFileSync14(path, "utf8");
5044
5179
  }
5045
5180
 
5046
5181
  // src/opencode/transform.ts
5047
5182
  init_esm_shims();
5048
5183
  import { existsSync as existsSync15, readdirSync as readdirSync2, readFileSync as readFileSync15, unlinkSync } from "fs";
5049
- import { join as join19 } from "path";
5184
+ import { join as join20 } from "path";
5050
5185
 
5051
5186
  // src/opencode/opencode-json.ts
5052
5187
  init_esm_shims();
5053
- function toLocal(cfg) {
5188
+ function toOpencode(cfg) {
5189
+ if (cfg.type === "http") {
5190
+ return cfg.auth === void 0 ? { type: "remote", url: cfg.url } : {
5191
+ type: "remote",
5192
+ url: cfg.url,
5193
+ oauth: false,
5194
+ headers: { Authorization: `Bearer {env:${cfg.auth.bearerEnv}}` }
5195
+ };
5196
+ }
5054
5197
  const local = { type: "local", command: [cfg.command, ...cfg.args] };
5055
5198
  if (cfg.env && Object.keys(cfg.env).length > 0) local.environment = { ...cfg.env };
5056
5199
  return local;
5057
5200
  }
5058
5201
  function renderOpencodeMcp(mcp) {
5059
5202
  return new Map(
5060
- Object.entries(mcp.mcpServers).map(([name, cfg]) => [`mcp.${name}`, toLocal(cfg)])
5203
+ Object.entries(mcp.mcpServers).map(([name, cfg]) => [`mcp.${name}`, toOpencode(cfg)])
5061
5204
  );
5062
5205
  }
5063
5206
  function renderOpencodeJson(params) {
5064
5207
  const config = parseTemplate(params.template);
5065
5208
  if (params.mcp) {
5066
5209
  config.mcp = Object.fromEntries(
5067
- Object.entries(params.mcp.mcpServers).map(([name, cfg]) => [name, toLocal(cfg)])
5210
+ Object.entries(params.mcp.mcpServers).map(([name, cfg]) => [name, toOpencode(cfg)])
5068
5211
  );
5069
5212
  }
5070
5213
  return `${JSON.stringify(config, null, 2)}
@@ -5095,7 +5238,7 @@ function runOpencodeTransform(params) {
5095
5238
  ...params.journal === void 0 ? {} : { journal: params.journal }
5096
5239
  });
5097
5240
  const opencodeTemplate = readRequired3(
5098
- join19(harnessRoot, "templates/opencode/opencode.json.template")
5241
+ join20(harnessRoot, "templates/opencode/opencode.json.template")
5099
5242
  );
5100
5243
  const { skillIds, skillWriter } = agentsSkillSlot({
5101
5244
  projectDir,
@@ -5104,7 +5247,7 @@ function runOpencodeTransform(params) {
5104
5247
  writer
5105
5248
  });
5106
5249
  ensureDir(projectDir);
5107
- const opencodeJsonPath = join19(projectDir, OPENCODE_JSON);
5250
+ const opencodeJsonPath = join20(projectDir, OPENCODE_JSON);
5108
5251
  const render = renderOpencodeMcp(mcp);
5109
5252
  const opencodeJson = writeShared({
5110
5253
  projectDir,
@@ -5123,13 +5266,13 @@ function runOpencodeTransform(params) {
5123
5266
  writer: skillWriter
5124
5267
  });
5125
5268
  const retiredCommands = [];
5126
- const cmdDir = join19(projectDir, ".opencode/commands");
5269
+ const cmdDir = join20(projectDir, ".opencode/commands");
5127
5270
  if (existsSync15(cmdDir)) {
5128
5271
  for (const entry of readdirSync2(cmdDir)) {
5129
5272
  if (!entry.endsWith(".md")) continue;
5130
5273
  const id = entry.slice(0, -3);
5131
- if (!existsSync15(join19(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
5132
- const victim = join19(cmdDir, entry);
5274
+ if (!existsSync15(join20(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
5275
+ const victim = join20(cmdDir, entry);
5133
5276
  if (writer.skipOutside(victim)) continue;
5134
5277
  try {
5135
5278
  backupFile(victim);
@@ -5172,6 +5315,14 @@ function trackDefaultMcpAssetIds(harnessRoot, tracks) {
5172
5315
  return id !== void 0 && anyTrack(tracks, r.pattern) ? [id] : [];
5173
5316
  });
5174
5317
  }
5318
+ function trackDefaultMcpNames(harnessRoot, tracks) {
5319
+ const { templateMcpPath } = mcpSources(harnessRoot);
5320
+ const base = existsSync16(templateMcpPath) ? Object.keys(JSON.parse(readFileSync16(templateMcpPath, "utf8")).mcpServers) : [];
5321
+ return /* @__PURE__ */ new Set([
5322
+ ...base,
5323
+ ...trackRows(harnessRoot).filter((r) => anyTrack(tracks, r.pattern)).map((r) => r.name)
5324
+ ]);
5325
+ }
5175
5326
  function trackRows(harnessRoot) {
5176
5327
  const { trackMapPath } = mcpSources(harnessRoot);
5177
5328
  return existsSync16(trackMapPath) ? parseTrackMcpMap(readFileSync16(trackMapPath, "utf8")) : [];
@@ -5194,8 +5345,8 @@ function adapterExcluded(harnessRoot, excluded) {
5194
5345
  }
5195
5346
  function mcpSources(harnessRoot) {
5196
5347
  return {
5197
- templateMcpPath: join20(harnessRoot, "templates/mcp.json"),
5198
- trackMapPath: join20(harnessRoot, "templates/track-mcp-map.tsv")
5348
+ templateMcpPath: join21(harnessRoot, "templates/mcp.json"),
5349
+ trackMapPath: join21(harnessRoot, "templates/track-mcp-map.tsv")
5199
5350
  };
5200
5351
  }
5201
5352
  var ALL_CLI_TARGETS = CLI_BASES;
@@ -5358,7 +5509,7 @@ function runCliTransforms(params) {
5358
5509
  // src/excluded-still-there.ts
5359
5510
  init_esm_shims();
5360
5511
  import { existsSync as existsSync17 } from "fs";
5361
- import { join as join21 } from "path";
5512
+ import { join as join22 } from "path";
5362
5513
  function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
5363
5514
  const out = [];
5364
5515
  const add = (id, catalog) => {
@@ -5371,10 +5522,11 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
5371
5522
  for (const id of excluded) {
5372
5523
  if (INTERNAL_BUNDLED_SKILL_IDS.includes(id)) {
5373
5524
  const there = [".claude/skills", ".agents/skills"].some(
5374
- (d2) => existsSync17(join21(projectDir, d2, id))
5525
+ (d2) => existsSync17(join22(projectDir, d2, id))
5375
5526
  );
5376
5527
  if (there) add(id, false);
5377
5528
  } else {
5529
+ if (Object.hasOwn(REMOVED_CATALOG_ASSETS, id)) continue;
5378
5530
  const asset = previousLog?.assets.find((a) => a.id === id);
5379
5531
  if (asset !== void 0 && assetStillThere(projectDir, asset)) add(id, true);
5380
5532
  }
@@ -5384,9 +5536,9 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
5384
5536
  function assetStillThere(projectDir, asset) {
5385
5537
  if (asset.method !== "skill" || asset.scope === "global") return true;
5386
5538
  if (asset.files !== void 0)
5387
- return asset.files.some((f) => existsSync17(join21(projectDir, f.path)));
5539
+ return asset.files.some((f) => existsSync17(join22(projectDir, f.path)));
5388
5540
  const dir = asset.detail.skill ?? asset.id;
5389
- return [".claude/skills", ".agents/skills"].some((d2) => existsSync17(join21(projectDir, d2, dir)));
5541
+ return [".claude/skills", ".agents/skills"].some((d2) => existsSync17(join22(projectDir, d2, dir)));
5390
5542
  }
5391
5543
 
5392
5544
  // src/external-installer.ts
@@ -5394,7 +5546,7 @@ init_esm_shims();
5394
5546
  import { spawnSync } from "child_process";
5395
5547
  import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
5396
5548
  import { homedir as homedir2 } from "os";
5397
- import { join as join22 } from "path";
5549
+ import { join as join23 } from "path";
5398
5550
  var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
5399
5551
  function skillRootOf(cli) {
5400
5552
  return cli === "claude" ? ".claude/skills" : ".agents/skills";
@@ -5402,11 +5554,11 @@ function skillRootOf(cli) {
5402
5554
  function snapshotSkillRoots(projectDir) {
5403
5555
  const out = /* @__PURE__ */ new Map();
5404
5556
  for (const root of SKILL_ROOTS) {
5405
- if (outsideProjectTarget(projectDir, join22(projectDir, root)) !== null) continue;
5406
- for (const rel of listFilesRecursive(join22(projectDir, root))) {
5557
+ if (outsideProjectTarget(projectDir, join23(projectDir, root)) !== null) continue;
5558
+ for (const rel of listFilesRecursive(join23(projectDir, root))) {
5407
5559
  const path = `${root}/${rel}`;
5408
5560
  try {
5409
- out.set(path, hashContent(readFileSync17(join22(projectDir, path), "utf8")));
5561
+ out.set(path, hashContent(readFileSync17(join23(projectDir, path), "utf8")));
5410
5562
  } catch {
5411
5563
  }
5412
5564
  }
@@ -5483,7 +5635,7 @@ function installOne(asset, ctx) {
5483
5635
  return runSpawn(asset, ctx.spawn, "npx", buildSkillArgs(method, ctx.cli, ctx.scope), cwd);
5484
5636
  }
5485
5637
  const outside = SKILL_ROOTS.flatMap((root) => {
5486
- const hit = outsideProjectTarget(cwd, join22(cwd, root));
5638
+ const hit = outsideProjectTarget(cwd, join23(cwd, root));
5487
5639
  return hit === null ? [] : [{ root, target: hit.target }];
5488
5640
  });
5489
5641
  const outRoots = new Set(outside.map((o) => o.root));
@@ -5664,7 +5816,7 @@ function detectVersion(method, spawn, scope, projectDir) {
5664
5816
  case "plugin":
5665
5817
  return detectPluginVersion(method.pluginId, scope, projectDir);
5666
5818
  case "npm": {
5667
- const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join22(projectDir, "node_modules", method.pkg, "package.json");
5819
+ const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join23(projectDir, "node_modules", method.pkg, "package.json");
5668
5820
  if (!pkgJson || !existsSync18(pkgJson)) return void 0;
5669
5821
  const parsed = JSON.parse(readFileSync17(pkgJson, "utf8"));
5670
5822
  return parsed.version;
@@ -5678,7 +5830,7 @@ function detectVersion(method, spawn, scope, projectDir) {
5678
5830
  }
5679
5831
  function detectPluginVersion(pluginId, scope, projectDir) {
5680
5832
  const claudeScope = scope === "global" ? "user" : "project";
5681
- const path = join22(homedir2(), ".claude/plugins/installed_plugins.json");
5833
+ const path = join23(homedir2(), ".claude/plugins/installed_plugins.json");
5682
5834
  if (!existsSync18(path)) return void 0;
5683
5835
  const data = JSON.parse(readFileSync17(path, "utf8"));
5684
5836
  const entries = data.plugins?.[pluginId] ?? [];
@@ -5693,7 +5845,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
5693
5845
  }
5694
5846
  function detectGlobalNpmPackageJsonPath(pkg, spawn) {
5695
5847
  const npmRoot = getNpmGlobalRoot(spawn);
5696
- return npmRoot ? join22(npmRoot, pkg, "package.json") : void 0;
5848
+ return npmRoot ? join23(npmRoot, pkg, "package.json") : void 0;
5697
5849
  }
5698
5850
  var npmGlobalRootCache;
5699
5851
  function getNpmGlobalRoot(spawn) {
@@ -5713,7 +5865,7 @@ function getNpmGlobalRoot(spawn) {
5713
5865
  // src/key-ids.ts
5714
5866
  init_esm_shims();
5715
5867
  import { readFileSync as readFileSync18 } from "fs";
5716
- import { join as join23 } from "path";
5868
+ import { join as join24 } from "path";
5717
5869
  function renderedKeyIds(harnessRoot, input, clis) {
5718
5870
  const out = /* @__PURE__ */ new Set();
5719
5871
  const add = (path, key) => {
@@ -5736,7 +5888,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
5736
5888
  for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
5737
5889
  for (const key of gitignoreRender().keys()) add(".gitignore", key);
5738
5890
  if (clis.includes("claude")) {
5739
- const template = readFileSync18(join23(harnessRoot, "templates/settings.json"), "utf8");
5891
+ const template = readFileSync18(join24(harnessRoot, "templates/settings.json"), "utf8");
5740
5892
  for (const key of renderSettingsPortion(template, () => true).keys()) {
5741
5893
  add(".claude/settings.json", key);
5742
5894
  }
@@ -5777,7 +5929,7 @@ function withoutAccepts(harnessRoot, spec, previous) {
5777
5929
 
5778
5930
  // src/linked-skill-bodies.ts
5779
5931
  init_esm_shims();
5780
- import { join as join24 } from "path";
5932
+ import { join as join25 } from "path";
5781
5933
  function refreshLinkedSkillBodies(params) {
5782
5934
  const { harnessRoot, projectDir, ids, baseline, journal } = params;
5783
5935
  const bodyKey = (id) => `.agents/skills/${id}/SKILL.md`;
@@ -5788,7 +5940,7 @@ function refreshLinkedSkillBodies(params) {
5788
5940
  );
5789
5941
  const result = writer.result();
5790
5942
  return {
5791
- updated: ours.filter((id) => written.has(join24(projectDir, bodyKey(id)))),
5943
+ updated: ours.filter((id) => written.has(join25(projectDir, bodyKey(id)))),
5792
5944
  notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
5793
5945
  files: result.files,
5794
5946
  backupPaths: result.backupPaths,
@@ -5800,7 +5952,7 @@ function refreshLinkedSkillBodies(params) {
5800
5952
  // src/out-of-track.ts
5801
5953
  init_esm_shims();
5802
5954
  import { lstatSync as lstatSync3, readFileSync as readFileSync19, rmSync as rmSync3 } from "fs";
5803
- import { join as join25 } from "path";
5955
+ import { join as join26 } from "path";
5804
5956
  var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
5805
5957
  var ANCHOR_RULE = "uzys-harness";
5806
5958
  function reclaimOutOfTrack(args) {
@@ -5820,7 +5972,7 @@ function reclaimOutOfTrack(args) {
5820
5972
  const name = AGENTS_RULE.exec(f.path)?.[1];
5821
5973
  if (name === void 0 || name === ANCHOR_RULE || rendered.has(name)) continue;
5822
5974
  if (isBaselineExcluded(`.claude/rules/${name}.md`, args.excluded)) continue;
5823
- const abs = join25(projectDir, f.path);
5975
+ const abs = join26(projectDir, f.path);
5824
5976
  if (args.outside.skip(abs)) continue;
5825
5977
  const stat = lstatSync3(abs, { throwIfNoEntry: false });
5826
5978
  if (stat !== void 0 && !stat.isFile()) continue;
@@ -5862,14 +6014,14 @@ function tracksBringing(rule) {
5862
6014
  // src/state.ts
5863
6015
  init_esm_shims();
5864
6016
  import { existsSync as existsSync19, readFileSync as readFileSync20 } from "fs";
5865
- import { join as join26 } from "path";
6017
+ import { join as join27 } from "path";
5866
6018
  var META_FILE = ".claude/.installed-tracks";
5867
6019
  var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
5868
6020
  var TRACE_RULES = ["htmx", "nextjs", "data-analysis", "pyside6", "cli-development"].map(
5869
6021
  (r) => `.claude/rules/${r}.md`
5870
6022
  );
5871
6023
  function detectInstallState(projectDir) {
5872
- const hasClaudeDir = existsSync19(join26(projectDir, ".claude"));
6024
+ const hasClaudeDir = existsSync19(join27(projectDir, ".claude"));
5873
6025
  const read = readInstallLogStatus(projectDir);
5874
6026
  if (read.status === "ok") {
5875
6027
  return {
@@ -5889,7 +6041,7 @@ function tracksFromLog(tracks) {
5889
6041
  return [...new Set(tracks.filter(isTrack))].sort();
5890
6042
  }
5891
6043
  function collectTraces(projectDir) {
5892
- const at5 = (rel) => join26(projectDir, rel);
6044
+ const at5 = (rel) => join27(projectDir, rel);
5893
6045
  const contains = (rel, needle) => existsSync19(at5(rel)) && readFileSync20(at5(rel), "utf8").includes(needle);
5894
6046
  const traces = [];
5895
6047
  if (existsSync19(at5(META_FILE)))
@@ -5958,7 +6110,7 @@ import {
5958
6110
  unlinkSync as unlinkSync2,
5959
6111
  writeFileSync as writeFileSync7
5960
6112
  } from "fs";
5961
- import { basename as basename3, dirname as dirname5, join as join27 } from "path";
6113
+ import { basename as basename3, dirname as dirname5, join as join28 } from "path";
5962
6114
  var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
5963
6115
  function buildUpdateSpec(projectDir, tracks, only) {
5964
6116
  const log = readInstallLog(projectDir);
@@ -5980,7 +6132,7 @@ function parseUpdateOnly(raw) {
5980
6132
  return { ok: true, groups: [...new Set(raw)] };
5981
6133
  }
5982
6134
  function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
5983
- const claudeDir = join27(projectDir, ".claude");
6135
+ const claudeDir = join28(projectDir, ".claude");
5984
6136
  const startedAt = Date.now();
5985
6137
  const backupsAtStart = new Set(
5986
6138
  collectRunBackups(projectDir, Number.NEGATIVE_INFINITY).map((b2) => b2.backup)
@@ -6026,8 +6178,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
6026
6178
  const policyBase = policyBaseline(projectDir);
6027
6179
  for (const { dir, ext } of POLICY_DIRS) {
6028
6180
  if (!claudeManaged || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
6029
- const target = join27(claudeDir, dir);
6030
- const source = join27(templatesDir, dir);
6181
+ const target = join28(claudeDir, dir);
6182
+ const source = join28(templatesDir, dir);
6031
6183
  const label = `.claude/${dir}`;
6032
6184
  const ctx = { prefix: dir, baseline: policyBase, outside, excluded: excludedIds(logAtStart) };
6033
6185
  const synced = updateDir(target, source, ext, ctx);
@@ -6041,8 +6193,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
6041
6193
  if (claudeManaged && syncedDirs.length > 0)
6042
6194
  refreshPolicyBaseline(projectDir, templatesDir, syncedDirs, outsidePaths(outside, ".claude/"));
6043
6195
  const skillSync = claudeManaged && wants("skills") ? syncSkills(
6044
- join27(claudeDir, "skills"),
6045
- join27(templatesDir, "skills"),
6196
+ join28(claudeDir, "skills"),
6197
+ join28(templatesDir, "skills"),
6046
6198
  skillBaseline(projectDir),
6047
6199
  /* @__PURE__ */ new Date(),
6048
6200
  (relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`),
@@ -6067,7 +6219,9 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
6067
6219
  const harnessMcp = renderHarnessMcp(harnessRoot, {
6068
6220
  spec: { tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS },
6069
6221
  excluded: excludedIds(logAtStart),
6070
- previousLog: logAtStart
6222
+ previousLog: logAtStart,
6223
+ // #722 (ADR-106) — 옛 로그면 하네스가 만든 `.mcp.json` 에서 하네스 값 그대로인 고를 수 있는 서버를 선택으로 본다
6224
+ projectDir
6071
6225
  });
6072
6226
  const sharedWrites = writeUpdateSharedFiles({
6073
6227
  projectDir,
@@ -6078,7 +6232,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
6078
6232
  droppedKeys: legacyDroppedKeys(logAtStart),
6079
6233
  harnessMcp
6080
6234
  });
6081
- const settingsPath = join27(claudeDir, "settings.json");
6235
+ const settingsPath = join28(claudeDir, "settings.json");
6082
6236
  if (claudeManaged && wants("hooks") && existsSync20(settingsPath) && !outside.skip(settingsPath)) {
6083
6237
  report.staleHookRefs = cleanStaleHookRefs(
6084
6238
  settingsPath,
@@ -6123,6 +6277,13 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
6123
6277
  report.restoredFiles = external.restoredFiles;
6124
6278
  report.excludedKeys = external.excludedKeys;
6125
6279
  report.sharedWrites = sharedWrites.writes;
6280
+ const remoteMcp = changedRemoteMcp(sharedWrites.writes);
6281
+ if (remoteMcp.length > 0) {
6282
+ report.remoteMcp = {
6283
+ servers: remoteMcp,
6284
+ clis: logAtStart === null ? [] : installedClis(logAtStart)
6285
+ };
6286
+ }
6126
6287
  report.legacyReleasedCatalog = [...legacyReleasedCatalog(logAtStart)];
6127
6288
  report.excludedStillThere = excludedStillThere(
6128
6289
  projectDir,
@@ -6165,6 +6326,9 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
6165
6326
  const skillRefresh = wants("external") ? (deps.refreshSkills ?? refreshExternalSkills)(projectDir) : { attempted: 0, refreshed: 0, failed: [], notInCatalog: [], unknown: false };
6166
6327
  report.externalSkillsRefreshed = skillRefresh.refreshed;
6167
6328
  report.externalSkillsFailed = skillRefresh.failed;
6329
+ const removedOnRecord = wants("external") ? catalogRemovedOnRecord(logAtStart) : [];
6330
+ if (removedOnRecord.length > 0) report.catalogRemoved = removedOnRecord;
6331
+ const saidAsRemoved = new Set(removedOnRecord.map((e2) => e2.id));
6168
6332
  report.externalSkillsNotInCatalog = [
6169
6333
  ...skillRefresh.notInCatalog,
6170
6334
  ...staleSkillDirs(
@@ -6172,7 +6336,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
6172
6336
  claudeDir,
6173
6337
  logAtStart === null ? [] : installedClis(logAtStart)
6174
6338
  ).filter((id) => !skillRefresh.notInCatalog.includes(id))
6175
- ];
6339
+ ].filter((id) => !saidAsRemoved.has(id));
6176
6340
  report.externalSkillsUnknown = skillRefresh.unknown;
6177
6341
  const outsideLinks = mergeOutside(outside.list(), sharedWrites.outside, external.externalOutside);
6178
6342
  if (outsideLinks.length > 0) report.outsideLinks = outsideLinks;
@@ -6271,7 +6435,7 @@ function retiredFeaturesPart(projectDir, f) {
6271
6435
  }
6272
6436
  function legacyHookOnDisk(projectDir, rel) {
6273
6437
  try {
6274
- return hasLegacyHarnessHook(readFileSync21(join27(projectDir, rel), "utf8"));
6438
+ return hasLegacyHarnessHook(readFileSync21(join28(projectDir, rel), "utf8"));
6275
6439
  } catch {
6276
6440
  return false;
6277
6441
  }
@@ -6297,7 +6461,7 @@ function writeUpdateSharedFiles(args) {
6297
6461
  excluded: adapterExcluded(harnessRoot, excluded)
6298
6462
  });
6299
6463
  const tracks = installedTracks(projectDir);
6300
- const settingsSource = join27(templatesDir, "settings.json");
6464
+ const settingsSource = join28(templatesDir, "settings.json");
6301
6465
  if (args.claudeManaged && wants("hooks") && existsSync20(settingsSource)) {
6302
6466
  const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
6303
6467
  const manifest = buildManifest(spec);
@@ -6308,12 +6472,20 @@ function writeUpdateSharedFiles(args) {
6308
6472
  log,
6309
6473
  (script) => {
6310
6474
  const target = `.claude/hooks/${script}`;
6311
- return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) && existsSync20(join27(projectDir, target));
6475
+ return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) && existsSync20(join28(projectDir, target));
6312
6476
  }
6313
6477
  // 기록이 claude 를 깔린 CLI 로 말한다 = install 이 이 파일을 썼다 — 없으면 만든다(훅 배선이 여기 산다)
6314
6478
  );
6315
6479
  }
6316
- if (wants("external")) writeMcpShared(writer, args.harnessMcp, log, { onlyIfRecorded: true });
6480
+ if (wants("external")) {
6481
+ writeMcpShared(
6482
+ writer,
6483
+ args.harnessMcp,
6484
+ log,
6485
+ { onlyIfRecorded: true },
6486
+ { tracks, names: trackDefaultMcpNames(harnessRoot, tracks) }
6487
+ );
6488
+ }
6317
6489
  if (wants("rules")) writeGitignoreShared(writer, log);
6318
6490
  const ledger = writer.ledger();
6319
6491
  if (ledger.portionPaths.length > 0 || ledger.rootFiles.length > 0) {
@@ -6344,10 +6516,28 @@ function writeUpdateSharedFiles(args) {
6344
6516
  outside: ledger.outside
6345
6517
  };
6346
6518
  }
6519
+ function catalogRemovedOnRecord(log) {
6520
+ if (log === null) return [];
6521
+ const clis = installedClis(log);
6522
+ const out = [];
6523
+ for (const a of log.assets) {
6524
+ if (!Object.hasOwn(REMOVED_CATALOG_ASSETS, a.id)) continue;
6525
+ if (out.some((e2) => e2.id === a.id)) continue;
6526
+ const replacedBy = REMOVED_CATALOG_ASSETS[a.id]?.replacedBy;
6527
+ const entry = { id: a.id, method: a.method };
6528
+ if (replacedBy !== void 0) {
6529
+ entry.replacedBy = replacedBy;
6530
+ const next = EXTERNAL_ASSETS.find((e2) => e2.id === replacedBy);
6531
+ if (next !== void 0) entry.replacementReaches = assetReachesCli(next, clis);
6532
+ }
6533
+ out.push(entry);
6534
+ }
6535
+ return out;
6536
+ }
6347
6537
  function staleSkillDirs(projectDir, claudeDir, clis) {
6348
6538
  const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
6349
- const dirs = [join27(claudeDir, "skills")];
6350
- if (clis.some((cli) => cli !== "claude")) dirs.push(join27(projectDir, ".agents", "skills"));
6539
+ const dirs = [join28(claudeDir, "skills")];
6540
+ if (clis.some((cli) => cli !== "claude")) dirs.push(join28(projectDir, ".agents", "skills"));
6351
6541
  const ids = /* @__PURE__ */ new Set();
6352
6542
  for (const dir of dirs) {
6353
6543
  if (!existsSync20(dir)) continue;
@@ -6358,13 +6548,13 @@ function staleSkillDirs(projectDir, claudeDir, clis) {
6358
6548
  return [...ids];
6359
6549
  }
6360
6550
  function staleAgentFiles(claudeDir) {
6361
- const dir = join27(claudeDir, "agents");
6551
+ const dir = join28(claudeDir, "agents");
6362
6552
  if (!existsSync20(dir)) return [];
6363
6553
  return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename3(e2.name, ".md")).filter((id) => RETIRED_AGENT_IDS.includes(id));
6364
6554
  }
6365
6555
  function demotedAgentFiles(claudeDir, tracks) {
6366
6556
  if (tracks.length === 0) return [];
6367
- const dir = join27(claudeDir, "agents");
6557
+ const dir = join28(claudeDir, "agents");
6368
6558
  if (!existsSync20(dir)) return [];
6369
6559
  const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
6370
6560
  ([id]) => id
@@ -6387,14 +6577,14 @@ function installNewAssets(projectDir, templatesDir, wants, outside) {
6387
6577
  if (!wants(group)) continue;
6388
6578
  if (entry.target === HARNESS_ANCHOR_FILE) continue;
6389
6579
  if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
6390
- const target = join27(projectDir, entry.target);
6580
+ const target = join28(projectDir, entry.target);
6391
6581
  const foreign = foreignOwnedTarget(projectDir, entry.target);
6392
6582
  if (foreign !== null) {
6393
6583
  if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
6394
6584
  continue;
6395
6585
  }
6396
6586
  if (existsSync20(target)) continue;
6397
- const source = join27(templatesDir, entry.source);
6587
+ const source = join28(templatesDir, entry.source);
6398
6588
  if (!existsSync20(source)) continue;
6399
6589
  if (outside.skip(target)) continue;
6400
6590
  if (entry.target.startsWith(".claude/")) {
@@ -6421,20 +6611,20 @@ function collectRunBackups(projectDir, startedAt, exclude) {
6421
6611
  const consider = (rel) => {
6422
6612
  if (!BACKUP_SUFFIX.test(rel)) return;
6423
6613
  if (exclude?.has(rel)) return;
6424
- const abs = join27(projectDir, rel);
6614
+ const abs = join28(projectDir, rel);
6425
6615
  if (statSync3(abs).mtimeMs < startedAt - 1e3) return;
6426
6616
  out.push({ path: rel.replace(BACKUP_SUFFIX, ""), backup: rel });
6427
6617
  };
6428
6618
  for (const e2 of readdirSync3(projectDir, { withFileTypes: true }))
6429
6619
  if (e2.isFile()) consider(e2.name);
6430
6620
  for (const dir of BACKUP_SCAN_DIRS) {
6431
- for (const rel of listFilesRecursive(join27(projectDir, dir))) consider(`${dir}/${rel}`);
6621
+ for (const rel of listFilesRecursive(join28(projectDir, dir))) consider(`${dir}/${rel}`);
6432
6622
  }
6433
6623
  return out.sort((a, b2) => a.path.localeCompare(b2.path));
6434
6624
  }
6435
6625
  function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new Set()) {
6436
- const dir = join27(projectDir, ".uzys-agent-harness");
6437
- const file = join27(dir, "update-backups.json");
6626
+ const dir = join28(projectDir, ".uzys-agent-harness");
6627
+ const file = join28(dir, "update-backups.json");
6438
6628
  try {
6439
6629
  if (backups.length === 0) {
6440
6630
  if (existsSync20(file)) unlinkSync2(file);
@@ -6472,14 +6662,14 @@ function installNewSkillDirs(projectDir, templatesDir, tracks, outside) {
6472
6662
  if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
6473
6663
  if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
6474
6664
  if (skillExcluded.has(entry.target.slice(".claude/skills/".length))) continue;
6475
- const target = join27(projectDir, entry.target);
6665
+ const target = join28(projectDir, entry.target);
6476
6666
  const foreign = foreignOwnedTarget(projectDir, entry.target);
6477
6667
  if (foreign !== null) {
6478
6668
  if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
6479
6669
  continue;
6480
6670
  }
6481
6671
  if (existsSync20(target)) continue;
6482
- const source = join27(templatesDir, entry.source);
6672
+ const source = join28(templatesDir, entry.source);
6483
6673
  if (!existsSync20(source)) continue;
6484
6674
  if (outside.skip(target)) continue;
6485
6675
  copyDir(source, target, (rel) => foreignOwnedTarget(projectDir, `${entry.target}/${rel}`));
@@ -6510,12 +6700,12 @@ function trackOnlyFileAssets(tracks) {
6510
6700
  );
6511
6701
  }
6512
6702
  function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @__PURE__ */ new Date()) {
6513
- if (existsSync20(join27(projectDir, LEGACY_ANCHOR_FILE2))) {
6703
+ if (existsSync20(join28(projectDir, LEGACY_ANCHOR_FILE2))) {
6514
6704
  report.legacyAnchor = LEGACY_ANCHOR_FILE2;
6515
6705
  }
6516
- const templateMd = join27(templatesDir, "CLAUDE.md");
6706
+ const templateMd = join28(templatesDir, "CLAUDE.md");
6517
6707
  if (!existsSync20(templateMd)) return;
6518
- const anchor = join27(projectDir, HARNESS_ANCHOR_FILE);
6708
+ const anchor = join28(projectDir, HARNESS_ANCHOR_FILE);
6519
6709
  if (outside.skip(anchor)) return;
6520
6710
  const existed = existsSync20(anchor);
6521
6711
  if (existed) {
@@ -6541,7 +6731,7 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
6541
6731
  recordAnchorBaseline(projectDir, anchor);
6542
6732
  }
6543
6733
  function upsertRootImport(projectDir, outside) {
6544
- const target = join27(projectDir, "CLAUDE.md");
6734
+ const target = join28(projectDir, "CLAUDE.md");
6545
6735
  if (outside.skip(target)) return false;
6546
6736
  const existing = existsSync20(target) ? readFileSync21(target, "utf8") : null;
6547
6737
  const next = upsertHarnessImport(existing, {
@@ -6550,7 +6740,7 @@ function upsertRootImport(projectDir, outside) {
6550
6740
  // ADR-085 — update 는 선택 목록을 인자로 받지 않는다. `syncSkills` 와 같은 판정을 쓴다:
6551
6741
  // `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
6552
6742
  continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
6553
- (id) => existsSync20(join27(projectDir, ".claude/skills", id))
6743
+ (id) => existsSync20(join28(projectDir, ".claude/skills", id))
6554
6744
  )
6555
6745
  });
6556
6746
  if (next === existing) return false;
@@ -6585,7 +6775,7 @@ function excludedBaselineOnDisk(projectDir, log) {
6585
6775
  if (excluded.size === 0) return [];
6586
6776
  const spec = buildAssetSpec({ tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS });
6587
6777
  return buildManifest(spec).filter(
6588
- (e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync20(join27(projectDir, e2.target))
6778
+ (e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync20(join28(projectDir, e2.target))
6589
6779
  ).map((e2) => e2.target);
6590
6780
  }
6591
6781
  function excludedSkillOf(log) {
@@ -6612,7 +6802,7 @@ function recordedRenderInputs(projectDir, log) {
6612
6802
  }
6613
6803
  function installedBundledSkills(projectDir) {
6614
6804
  return INTERNAL_BUNDLED_SKILL_IDS.filter(
6615
- (id) => existsSync20(join27(projectDir, ".claude/skills", id)) || existsSync20(join27(projectDir, ".agents/skills", id))
6805
+ (id) => existsSync20(join28(projectDir, ".claude/skills", id)) || existsSync20(join28(projectDir, ".agents/skills", id))
6616
6806
  );
6617
6807
  }
6618
6808
  function installedCliTargets(log) {
@@ -6642,9 +6832,9 @@ var SHARED_SKILLS_DIR = ".agents/skills";
6642
6832
  var SHARED_SKILL_FILE = /^\.agents\/skills\/([^/]+)\//;
6643
6833
  var SHARED_SKILL_MD = /^\.agents\/skills\/([^/]+)\/SKILL\.md$/;
6644
6834
  function sharedSkillIdsOnDisk(projectDir) {
6645
- const root = join27(projectDir, SHARED_SKILLS_DIR);
6835
+ const root = join28(projectDir, SHARED_SKILLS_DIR);
6646
6836
  if (!existsSync20(root)) return /* @__PURE__ */ new Set();
6647
- return new Set(readdirSync3(root).filter((id) => existsSync20(join27(root, id, "SKILL.md"))));
6837
+ return new Set(readdirSync3(root).filter((id) => existsSync20(join28(root, id, "SKILL.md"))));
6648
6838
  }
6649
6839
  var CREATED_SHARED_HOW = {
6650
6840
  "opencode.json": "created from template (harness part + template defaults)",
@@ -6661,18 +6851,24 @@ function agentsMdNews(report, existedBefore, priorPaths) {
6661
6851
  if (!created && report.agentsMdSeededFrom === null) return null;
6662
6852
  return { created, clis: [...report.clis], seededFrom: report.agentsMdSeededFrom };
6663
6853
  }
6854
+ function changedRemoteMcp(writes) {
6855
+ const mcp = writes.find((w3) => w3.path === ".mcp.json");
6856
+ if (mcp === void 0) return [];
6857
+ const changed = /* @__PURE__ */ new Set([...mcp.added, ...mcp.replaced ?? []]);
6858
+ return (mcp.remote ?? []).filter((r) => changed.has(r.name));
6859
+ }
6664
6860
  function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
6665
6861
  const log = readInstallLog(projectDir);
6666
6862
  const presentBefore = sharedSkillIdsOnDisk(projectDir);
6667
6863
  const rulesBefore = /* @__PURE__ */ new Set();
6668
- const rulesDir = join27(projectDir, ".agents", "rules");
6864
+ const rulesDir = join28(projectDir, ".agents", "rules");
6669
6865
  if (existsSync20(rulesDir)) {
6670
6866
  try {
6671
6867
  for (const f of readdirSync3(rulesDir)) rulesBefore.add(f);
6672
6868
  } catch {
6673
6869
  }
6674
6870
  }
6675
- const agentsMdBefore = existsSync20(join27(projectDir, "AGENTS.md"));
6871
+ const agentsMdBefore = existsSync20(join28(projectDir, "AGENTS.md"));
6676
6872
  const result = runCliTransforms({
6677
6873
  harnessRoot,
6678
6874
  projectDir,
@@ -6814,8 +7010,8 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
6814
7010
  const backedUp = [];
6815
7011
  for (const file of readdirSync3(target)) {
6816
7012
  if (!file.endsWith(ext)) continue;
6817
- const targetFile = join27(target, file);
6818
- const sourceFile = join27(source, file);
7013
+ const targetFile = join28(target, file);
7014
+ const sourceFile = join28(source, file);
6819
7015
  if (!existsSync20(sourceFile)) continue;
6820
7016
  if (ctx.outside?.skip(targetFile)) continue;
6821
7017
  if (ctx.excluded && isBaselineExcluded(`.claude/${ctx.prefix}/${file}`, ctx.excluded)) continue;
@@ -6841,22 +7037,22 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
6841
7037
  const pruned = [];
6842
7038
  for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
6843
7039
  if (!skill.isDirectory()) continue;
6844
- const targetSkill = join27(targetDir, skill.name);
7040
+ const targetSkill = join28(targetDir, skill.name);
6845
7041
  if (occupiedByNonDirectory(targetSkill)) {
6846
7042
  skippedLinks.push(skill.name);
6847
7043
  continue;
6848
7044
  }
6849
7045
  if (!existsSync20(targetSkill)) continue;
6850
7046
  if (excludedSkill?.(skill.name)) continue;
6851
- for (const rel of listFilesRecursive(join27(sourceDir, skill.name))) {
6852
- const targetFile = join27(targetSkill, rel);
7047
+ for (const rel of listFilesRecursive(join28(sourceDir, skill.name))) {
7048
+ const targetFile = join28(targetSkill, rel);
6853
7049
  const foreign = foreignOf(`${skill.name}/${rel}`);
6854
7050
  if (foreign !== null) {
6855
7051
  if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
6856
7052
  continue;
6857
7053
  }
6858
7054
  if (outside?.skip(targetFile)) continue;
6859
- const next = readFileSync21(join27(sourceDir, skill.name, rel), "utf8");
7055
+ const next = readFileSync21(join28(sourceDir, skill.name, rel), "utf8");
6860
7056
  if (!existsSync20(targetFile)) {
6861
7057
  mkdirSync5(dirname5(targetFile), { recursive: true });
6862
7058
  writeFileSync7(targetFile, next);
@@ -6873,11 +7069,11 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
6873
7069
  writeFileSync7(targetFile, next);
6874
7070
  updated++;
6875
7071
  }
6876
- const bundled = new Set(listFilesRecursive(join27(sourceDir, skill.name)));
7072
+ const bundled = new Set(listFilesRecursive(join28(sourceDir, skill.name)));
6877
7073
  for (const rel of listFilesRecursive(targetSkill)) {
6878
7074
  if (bundled.has(rel) || basename3(rel).includes(".backup-")) continue;
6879
7075
  if (foreignOf(`${skill.name}/${rel}`) !== null) continue;
6880
- const targetFile = join27(targetSkill, rel);
7076
+ const targetFile = join28(targetSkill, rel);
6881
7077
  if (outside?.skip(targetFile)) continue;
6882
7078
  const recorded2 = baseline.get(`${skill.name}/${rel}`);
6883
7079
  if (recorded2 === void 0 || hashContent(readFileSync21(targetFile, "utf8")) !== recorded2) {
@@ -6944,9 +7140,9 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
6944
7140
  const removed = [];
6945
7141
  for (const file of readdirSync3(target)) {
6946
7142
  if (!file.endsWith(ext)) continue;
6947
- const sourceFile = join27(source, file);
7143
+ const sourceFile = join28(source, file);
6948
7144
  if (!existsSync20(sourceFile)) {
6949
- const targetFile = join27(target, file);
7145
+ const targetFile = join28(target, file);
6950
7146
  if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
6951
7147
  if (ctx.outside?.skip(targetFile)) continue;
6952
7148
  try {
@@ -6961,7 +7157,7 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
6961
7157
  return removed;
6962
7158
  }
6963
7159
  function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
6964
- const target = join27(projectDir, ".mcp-allowlist");
7160
+ const target = join28(projectDir, ".mcp-allowlist");
6965
7161
  if (!existsSync20(target)) return null;
6966
7162
  try {
6967
7163
  const backup = backupFile(target, now);
@@ -6977,8 +7173,8 @@ function refreshNeutralHelpers(projectDir, templatesDir, report) {
6977
7173
  for (const entry of buildManifest(spec)) {
6978
7174
  if (entry.type !== "file" || !isCliNeutralTarget(entry.target)) continue;
6979
7175
  if (!entry.applies(spec)) continue;
6980
- const abs = join27(projectDir, entry.target);
6981
- const source = join27(templatesDir, entry.source);
7176
+ const abs = join28(projectDir, entry.target);
7177
+ const source = join28(templatesDir, entry.source);
6982
7178
  if (!existsSync20(source)) continue;
6983
7179
  let st2;
6984
7180
  try {
@@ -7034,7 +7230,7 @@ function dropHealedHookPortions(projectDir, healedFiles) {
7034
7230
  const log = readInstallLog(projectDir);
7035
7231
  if (log === null) return;
7036
7232
  const portions = (log.portions ?? []).filter(
7037
- (p2) => !(p2.path === join27(".claude", "settings.json") && isHealedKey(p2.key))
7233
+ (p2) => !(p2.path === join28(".claude", "settings.json") && isHealedKey(p2.key))
7038
7234
  );
7039
7235
  if (portions.length > 0) log.portions = portions;
7040
7236
  else delete log.portions;
@@ -7048,11 +7244,11 @@ function dropHealedHookPortions(projectDir, healedFiles) {
7048
7244
  function runInstall(ctx) {
7049
7245
  const { harnessRoot, projectDir } = ctx;
7050
7246
  const mode = ctx.mode ?? "fresh";
7051
- const templatesDir = join28(harnessRoot, "templates");
7247
+ const templatesDir = join29(harnessRoot, "templates");
7052
7248
  if (!existsSync21(templatesDir)) {
7053
7249
  throw new Error(`Templates dir not found: ${templatesDir}`);
7054
7250
  }
7055
- const claudeDir = join28(projectDir, ".claude");
7251
+ const claudeDir = join29(projectDir, ".claude");
7056
7252
  const previousLog = readInstallLog(projectDir);
7057
7253
  const legacyAnchor = mode === "update" ? null : legacyAnchorTrace(projectDir, ctx.spec);
7058
7254
  if (mode === "update" && previousLog === null) {
@@ -7081,7 +7277,7 @@ function runInstall(ctx) {
7081
7277
  const manifestSpec = buildManifestSpec(spec);
7082
7278
  const baselineExcluded = new Set(spec.baselineExclude ?? []);
7083
7279
  if (spec.cli.includes("claude")) {
7084
- const settingsPath = join28(projectDir, ".claude", "settings.json");
7280
+ const settingsPath = join29(projectDir, ".claude", "settings.json");
7085
7281
  if (existsSync21(settingsPath)) {
7086
7282
  try {
7087
7283
  JSON.parse(readFileSync22(settingsPath, "utf8"));
@@ -7140,8 +7336,11 @@ function runInstallStages(ctx, args) {
7140
7336
  installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, writer)
7141
7337
  );
7142
7338
  stage.rootImportWritten = base.rootImportWritten;
7143
- const harnessMcp = renderHarnessMcp(harnessRoot, { spec, excluded, previousLog });
7144
- const mcp = writeMcpPortion(writer, harnessMcp, previousLog);
7339
+ const harnessMcp = renderHarnessMcp(harnessRoot, { spec, excluded, previousLog, projectDir });
7340
+ const mcp = writeMcpPortion(writer, harnessMcp, previousLog, {
7341
+ tracks: spec.tracks,
7342
+ names: trackDefaultMcpNames(harnessRoot, spec.tracks)
7343
+ });
7145
7344
  const ciScaffold = isAssetSelected("ci-scaffold", {
7146
7345
  tracks: spec.tracks,
7147
7346
  options: spec.options,
@@ -7358,7 +7557,7 @@ function releasedThisRun(args) {
7358
7557
  }
7359
7558
  if (id.startsWith(BASELINE_PREFIX)) {
7360
7559
  const again2 = manifest.some(
7361
- (e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync21(join28(projectDir, e2.target))
7560
+ (e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync21(join29(projectDir, e2.target))
7362
7561
  );
7363
7562
  return { id, again: again2, tail: "the harness manages it again" };
7364
7563
  }
@@ -7367,7 +7566,7 @@ function releasedThisRun(args) {
7367
7566
  options: spec.options,
7368
7567
  ...spec.userOverride ? { userOverride: spec.userOverride } : {}
7369
7568
  });
7370
- const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join28(projectDir, d2, id)));
7569
+ const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join29(projectDir, d2, id)));
7371
7570
  return {
7372
7571
  id,
7373
7572
  again,
@@ -7379,10 +7578,10 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
7379
7578
  const done = new Set(touched);
7380
7579
  return keyExclude.flatMap((id) => {
7381
7580
  const path = sharedPathOfKeyId(id);
7382
- if (path === null || done.has(path) || !existsSync21(join28(projectDir, path))) return [];
7581
+ if (path === null || done.has(path) || !existsSync21(join29(projectDir, path))) return [];
7383
7582
  const name = adapterFor(path);
7384
7583
  const keys = [...excludedKeys(path, [id])];
7385
- const present2 = name === null ? null : ADAPTERS[name].read(readFileSync22(join28(projectDir, path), "utf8"), keys, projectDir);
7584
+ const present2 = name === null ? null : ADAPTERS[name].read(readFileSync22(join29(projectDir, path), "utf8"), keys, projectDir);
7386
7585
  return present2 !== null && present2.size === 0 ? [] : [{ id, path }];
7387
7586
  });
7388
7587
  }
@@ -7426,7 +7625,7 @@ function installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, w
7426
7625
  continue;
7427
7626
  }
7428
7627
  if (!isCliNeutralTarget(entry.target)) continue;
7429
- const source = join28(templatesDir, entry.source);
7628
+ const source = join29(templatesDir, entry.source);
7430
7629
  if (!existsSync21(source)) {
7431
7630
  result.skipped += 1;
7432
7631
  continue;
@@ -7447,13 +7646,13 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
7447
7646
  if (!entry.applies(manifestSpec)) {
7448
7647
  continue;
7449
7648
  }
7450
- const target = join28(projectDir, entry.target);
7649
+ const target = join29(projectDir, entry.target);
7451
7650
  if (isBaselineExcluded(entry.target, baselineExcluded)) {
7452
7651
  result.excluded.push(entry.target);
7453
7652
  if (existsSync21(target)) result.excludedOnDisk.push(entry.target);
7454
7653
  continue;
7455
7654
  }
7456
- const source = join28(templatesDir, entry.source);
7655
+ const source = join29(templatesDir, entry.source);
7457
7656
  if (!existsSync21(source)) {
7458
7657
  result.skipped += 1;
7459
7658
  continue;
@@ -7484,18 +7683,18 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
7484
7683
  if (!result.foreignOwned.includes(slot)) result.foreignOwned.push(slot);
7485
7684
  continue;
7486
7685
  }
7487
- if (writer.skipOutside(join28(projectDir, entry.target, rel))) {
7686
+ if (writer.skipOutside(join29(projectDir, entry.target, rel))) {
7488
7687
  leftOutside += 1;
7489
7688
  continue;
7490
7689
  }
7491
- writer.harness(`${entry.target}/${rel}`, { source: join28(source, rel) });
7690
+ writer.harness(`${entry.target}/${rel}`, { source: join29(source, rel) });
7492
7691
  }
7493
7692
  if (leftOutside > 0) continue;
7494
7693
  result.dirsCopied += 1;
7495
7694
  }
7496
7695
  accumulateCategory(result.categories, entry);
7497
7696
  }
7498
- const hookDir = join28(projectDir, ".claude/hooks");
7697
+ const hookDir = join29(projectDir, ".claude/hooks");
7499
7698
  if (existsSync21(hookDir)) {
7500
7699
  chmodHooksSync(hookDir, projectDir);
7501
7700
  }
@@ -7512,12 +7711,12 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
7512
7711
  if (settingsSource !== null) {
7513
7712
  writeSettingsPortion(writer, settingsSource, projectDir, previousLog, (script) => {
7514
7713
  const target = `${HOOKS_PREFIX}${script}`;
7515
- return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) && existsSync21(join28(projectDir, target));
7714
+ return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) && existsSync21(join29(projectDir, target));
7516
7715
  });
7517
7716
  result.keptHookRefs.push(
7518
7717
  ...findStaleHookRefs(
7519
- join28(projectDir, ".claude", "settings.json"),
7520
- join28(projectDir, ".claude")
7718
+ join29(projectDir, ".claude", "settings.json"),
7719
+ join29(projectDir, ".claude")
7521
7720
  )
7522
7721
  );
7523
7722
  }
@@ -7527,7 +7726,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
7527
7726
  // ADR-085 · update(`upsertRootImport`)와 같은 판정 — 실제로 깔린 스킬만 안내한다. 밖 링크라 쓰지 않은
7528
7727
  // 스킬(ADR-098)을 "installed" 라 적으면 다음 update 가 그 절을 걷어 내 두 동작이 서로 반대로 쓴다.
7529
7728
  (manifestSpec.selectedInternalSkills ?? []).filter(
7530
- (id) => existsSync21(join28(projectDir, ".claude", "skills", id))
7729
+ (id) => existsSync21(join29(projectDir, ".claude", "skills", id))
7531
7730
  ),
7532
7731
  harnessRoot,
7533
7732
  writer
@@ -7549,8 +7748,8 @@ function writeSettingsPortion(writer, source, projectDir, previousLog, hookInsta
7549
7748
  hookInstalled
7550
7749
  );
7551
7750
  }
7552
- function writeMcpPortion(writer, harnessMcp, previousLog) {
7553
- return [...writeMcpShared(writer, harnessMcp, previousLog).harness].sort();
7751
+ function writeMcpPortion(writer, harnessMcp, previousLog, defaults) {
7752
+ return [...writeMcpShared(writer, harnessMcp, previousLog, {}, defaults).harness].sort();
7554
7753
  }
7555
7754
  function installedTracksText(tracks) {
7556
7755
  return `${[...new Set(tracks)].sort().join("\n")}
@@ -7799,7 +7998,7 @@ function accumulateCategory(cats, entry) {
7799
7998
  }
7800
7999
  }
7801
8000
  function writeRootClaudeMd(projectDir, tracks, continuousSkills, harnessRoot, writer) {
7802
- const target = join28(projectDir, "CLAUDE.md");
8001
+ const target = join29(projectDir, "CLAUDE.md");
7803
8002
  if (writer.skipOutside(target)) return null;
7804
8003
  const existing = existsSync21(target) ? readFileSync22(target, "utf-8") : null;
7805
8004
  const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
@@ -7836,7 +8035,7 @@ function listHookFiles(hookDir) {
7836
8035
  init_esm_shims();
7837
8036
  import { homedir as homedir3 } from "os";
7838
8037
  import { existsSync as existsSync23, readdirSync as readdirSync5, readFileSync as readFileSync24 } from "fs";
7839
- import { join as join30, relative as relative4 } from "path";
8038
+ import { join as join31, relative as relative4 } from "path";
7840
8039
 
7841
8040
  // src/preset-recommend.ts
7842
8041
  init_esm_shims();
@@ -7873,9 +8072,9 @@ function groupAssetsByCategory(assetIds) {
7873
8072
  // src/resident-entries.ts
7874
8073
  init_esm_shims();
7875
8074
  import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
7876
- import { basename as basename5, join as join29 } from "path";
8075
+ import { basename as basename5, join as join30 } from "path";
7877
8076
  function residentEntries(spec) {
7878
- const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync22(join29(spec.projectDir, d2, id)));
8077
+ const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync22(join30(spec.projectDir, d2, id)));
7879
8078
  const forceExclude = (spec.userOverride?.forceExclude ?? []).filter((id) => !skillThere(id));
7880
8079
  const assetSpec = buildManifestSpec(
7881
8080
  spec.userOverride ? { ...spec, userOverride: { ...spec.userOverride, forceExclude } } : spec
@@ -7901,7 +8100,7 @@ function residentEntries(spec) {
7901
8100
  function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
7902
8101
  if (contextFiles !== void 0) return residentCost(entries, void 0, spec.cli, contextFiles);
7903
8102
  const cost = residentCost(entries, void 0, spec.cli, plannedClaudeContext(spec));
7904
- if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync22(join29(spec.projectDir, "AGENTS.md"))) {
8103
+ if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync22(join30(spec.projectDir, "AGENTS.md"))) {
7905
8104
  return makeResidentCost({
7906
8105
  ...cost,
7907
8106
  contextNote: "planned, not counting text already in AGENTS.md"
@@ -7912,7 +8111,7 @@ function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
7912
8111
  function plannedClaudeContext(spec) {
7913
8112
  if (!spec.cli.includes("claude")) return void 0;
7914
8113
  const root = resolveBundleRoot();
7915
- const rootClaude = join29(spec.projectDir, "CLAUDE.md");
8114
+ const rootClaude = join30(spec.projectDir, "CLAUDE.md");
7916
8115
  const existing = existsSync22(rootClaude) ? readFileSync23(rootClaude, "utf-8") : null;
7917
8116
  const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
7918
8117
  if (existing === null && seeded === null) return void 0;
@@ -7923,14 +8122,14 @@ function plannedClaudeContext(spec) {
7923
8122
  continuousSkills: manifestSpec.selectedInternalSkills ?? [],
7924
8123
  ...seeded === null ? {} : { projectContext: seeded }
7925
8124
  });
7926
- return [join29(root, "templates", "CLAUDE.md"), { text }];
8125
+ return [join30(root, "templates", "CLAUDE.md"), { text }];
7927
8126
  }
7928
8127
  function onDiskTargets(spec, assetSpec) {
7929
8128
  const out = /* @__PURE__ */ new Set();
7930
8129
  for (const e2 of buildManifest(assetSpec)) {
7931
8130
  if (!e2.applies(assetSpec)) continue;
7932
8131
  const t = classifyBaselineTarget(e2.target);
7933
- if (t !== null && existsSync22(join29(spec.projectDir, e2.target))) out.add(t.id);
8132
+ if (t !== null && existsSync22(join30(spec.projectDir, e2.target))) out.add(t.id);
7934
8133
  }
7935
8134
  return out;
7936
8135
  }
@@ -8052,6 +8251,11 @@ function renderUpdateSummary(log, spec, report) {
8052
8251
  summarizeContextCost(finalSelectedAssets(spec.tracks, spec.userOverride)).unmeasuredCount
8053
8252
  );
8054
8253
  if (cost) log(infoRow("CONTEXT", cost));
8254
+ const remote = report.updateMode?.remoteMcp;
8255
+ const remoteLines = remote === void 0 ? [] : remoteMcpNextLines(remote.servers, remote.clis);
8256
+ remoteLines.forEach((line, i) => {
8257
+ log(i === 0 ? infoRow("NEXT", line) : `${" ".repeat(16)} ${line}`);
8258
+ });
8055
8259
  const backups = report.updateMode?.backups ?? [];
8056
8260
  if (backups.length > 0) {
8057
8261
  const unmeasured = report.updateMode?.noChecksum?.length ?? 0;
@@ -8079,23 +8283,23 @@ function residentEntriesOnDisk(spec) {
8079
8283
  const hasClaude = spec.cli.includes("claude");
8080
8284
  const surface = anchorSurface(spec.cli).scaffold;
8081
8285
  const mdFiles = (dir, kind, skip) => {
8082
- const abs = join30(spec.projectDir, dir);
8286
+ const abs = join31(spec.projectDir, dir);
8083
8287
  return existsSync23(abs) ? readdirSync5(abs, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md") && e2.name !== skip).map((e2) => ({
8084
8288
  source: `${kind}/${e2.name}`,
8085
8289
  // `target` 은 `residentCost` 의 분류 키다 — 실제 자리는 `file` 이 말한다.
8086
8290
  target: `.claude/${kind}/${e2.name}`,
8087
8291
  // 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 본문도,
8088
8292
  // 배포판에 더는 없는 파일도 실제로 상주하는 것은 이쪽이다.
8089
- file: join30(abs, e2.name)
8293
+ file: join31(abs, e2.name)
8090
8294
  })) : [];
8091
8295
  };
8092
- const rules = surface === "claude" ? mdFiles(join30(".claude", "rules"), "rules") : rulesInline(spec.cli) ? planned.filter((e2) => e2.target.startsWith(".claude/rules/")) : mdFiles(join30(".agents", "rules"), "rules", "uzys-harness.md");
8093
- const slot = hasClaude ? join30(".claude", "skills") : join30(".agents", "skills");
8296
+ const rules = surface === "claude" ? mdFiles(join31(".claude", "rules"), "rules") : rulesInline(spec.cli) ? planned.filter((e2) => e2.target.startsWith(".claude/rules/")) : mdFiles(join31(".agents", "rules"), "rules", "uzys-harness.md");
8297
+ const slot = hasClaude ? join31(".claude", "skills") : join31(".agents", "skills");
8094
8298
  const skills = planned.filter((e2) => e2.target.startsWith(".claude/skills/")).map((e2) => ({
8095
8299
  ...e2,
8096
- file: join30(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
8300
+ file: join31(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
8097
8301
  })).filter((e2) => existsSync23(e2.file));
8098
- const agents = hasClaude ? mdFiles(join30(".claude", "agents"), "agents") : [];
8302
+ const agents = hasClaude ? mdFiles(join31(".claude", "agents"), "agents") : [];
8099
8303
  return [
8100
8304
  ...planned.filter(
8101
8305
  (e2) => !e2.target.startsWith(".claude/rules/") && !e2.target.startsWith(".claude/agents/") && !e2.target.startsWith(".claude/skills/")
@@ -8107,8 +8311,8 @@ function residentEntriesOnDisk(spec) {
8107
8311
  }
8108
8312
  function contextFilesOnDisk(spec) {
8109
8313
  const surface = anchorSurface(spec.cli).scaffold;
8110
- const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join30(".claude", "CLAUDE.md")] : ["AGENTS.md"];
8111
- return files.map((f) => join30(spec.projectDir, f));
8314
+ const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join31(".claude", "CLAUDE.md")] : ["AGENTS.md"];
8315
+ return files.map((f) => join31(spec.projectDir, f));
8112
8316
  }
8113
8317
  function rulesInsideAgentsMd(spec, cost) {
8114
8318
  if (anchorSurface(spec.cli).scaffold !== "agents-md" || !rulesInline(spec.cli)) return cost;
@@ -8362,8 +8566,8 @@ function renderFinalSummary(log, spec, report, fromWizard) {
8362
8566
  const codexTrusted = report.codexOptIn?.trustEntry.status === "registered" || report.codexOptIn?.trustEntry.status === "already-present";
8363
8567
  const alreadyTrustedGlobally = (() => {
8364
8568
  try {
8365
- const home = process.env.CODEX_HOME ?? join30(homedir3(), ".codex");
8366
- const configPath = join30(home, "config.toml");
8569
+ const home = process.env.CODEX_HOME ?? join31(homedir3(), ".codex");
8570
+ const configPath = join31(home, "config.toml");
8367
8571
  if (!existsSync23(configPath)) return false;
8368
8572
  return hasTrustEntry(readFileSync24(configPath, "utf8"), spec.projectDir);
8369
8573
  } catch {
@@ -8387,8 +8591,10 @@ function renderFinalSummary(log, spec, report, fromWizard) {
8387
8591
  `${" ".repeat(16)} ${c.bold("Codex")} also runs the harness session-start hook only after you review it: at first launch it warns that hooks need review \u2014 open ${c.bold("/hooks")} and trust it. The harness does not approve it for you.`
8388
8592
  );
8389
8593
  }
8594
+ const remote = (report.shared ?? []).find((f) => f.path === ".mcp.json")?.remote ?? [];
8595
+ for (const line of remoteMcpNextLines(remote, spec.cli)) log(`${" ".repeat(16)} ${line}`);
8390
8596
  const scaffoldFiles = scaffoldFilesForCli(spec.cli).filter(
8391
- (f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join30(spec.projectDir, f))
8597
+ (f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join31(spec.projectDir, f))
8392
8598
  );
8393
8599
  if (scaffoldFiles.length > 0) {
8394
8600
  const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
@@ -8734,6 +8940,7 @@ function renderPhase1Rows(log, baseline, verbose = false) {
8734
8940
  const meta = failed.length === 1 ? `${head.id}: ${head.message}` : `${failed.map((f) => f.id).join(", ")} (${failed.length}\uAC74) \xB7 \uC608: ${head.message}`;
8735
8941
  log(assetRow("skip", "external skills", `${meta} \xB7 \uB2E4\uC74C update \uC5D0\uC11C \uB2E4\uC2DC \uC2DC\uB3C4\uD55C\uB2E4`));
8736
8942
  }
8943
+ for (const row of catalogRemovedRows(baseline.updateMode.catalogRemoved ?? [])) log(row);
8737
8944
  if (baseline.updateMode.externalSkillsNotInCatalog.length > 0) {
8738
8945
  const ids = baseline.updateMode.externalSkillsNotInCatalog;
8739
8946
  for (const id of ids) {
@@ -8979,6 +9186,7 @@ function sharedFileRow(f) {
8979
9186
  if (f.verdict === "leave+advise") return ` ${c.yellow(symbol.skip)} left ${f.path} \u2014 ${f.line}`;
8980
9187
  const parts = [f.line];
8981
9188
  if (f.added.length > 0) parts.push(`added: ${f.added.join(", ")}`);
9189
+ if (f.path === ".mcp.json") parts.push(...mcpChangeParts(f));
8982
9190
  if (f.kept.length > 0) parts.push(`kept yours: ${f.kept.join(", ")}`);
8983
9191
  if (f.restored.length > 0) parts.push(restoredKeysPart(f.restored));
8984
9192
  parts.push(...excludedKeyParts(f));
@@ -8989,6 +9197,47 @@ ${assetRow("success", f.path, GITIGNORE_RECORD_DIR_NOTE, 28)}`;
8989
9197
  }
8990
9198
  return row;
8991
9199
  }
9200
+ function mcpChangeParts(f) {
9201
+ const why = (name) => f.notes?.[name];
9202
+ const parts = [];
9203
+ const replaced = f.replaced ?? [];
9204
+ if (replaced.length > 0) {
9205
+ parts.push(
9206
+ `updated: ${replaced.map((n) => why(n) === void 0 ? n : `${n} (${why(n)})`).join(", ")}`
9207
+ );
9208
+ }
9209
+ for (const n of f.retired ?? []) {
9210
+ parts.push(why(n) === void 0 ? `retired: ${n}` : `retired: ${n} \u2014 ${why(n)}`);
9211
+ }
9212
+ return parts;
9213
+ }
9214
+ function remoteMcpNextLines(servers, clis) {
9215
+ const reads = clis.filter((c3) => c3 === "claude" || c3 === "codex" || c3 === "opencode");
9216
+ if (servers.length === 0 || reads.length === 0) return [];
9217
+ const oauth = servers.filter((s) => s.bearerEnv === void 0).map((s) => s.name);
9218
+ const lines2 = [];
9219
+ const one = oauth[0];
9220
+ if (one !== void 0) {
9221
+ lines2.push(`Remote MCP servers sign in on first use \u2014 ${oauth.join(" \xB7 ")}`);
9222
+ if (reads.includes("claude"))
9223
+ lines2.push(
9224
+ ` Claude Code: approve the project's .mcp.json servers when asked, then /mcp \u2192 Authenticate (or: claude mcp login ${one})`
9225
+ );
9226
+ if (reads.includes("codex"))
9227
+ lines2.push(
9228
+ ` Codex: ${oauth.map((n) => `codex mcp login ${n}`).join(" \xB7 ")} (after trusting this folder)`
9229
+ );
9230
+ if (reads.includes("opencode"))
9231
+ lines2.push(` OpenCode: opens the browser by itself (or: opencode mcp auth ${one})`);
9232
+ }
9233
+ for (const s of servers) {
9234
+ if (s.bearerEnv === void 0) continue;
9235
+ lines2.push(
9236
+ `${s.name} needs ${s.bearerEnv} in the shell you start the CLI from \u2014 a GitHub personal access token (fine-grained: https://github.com/settings/personal-access-tokens/new). The harness writes only the variable name, never the token`
9237
+ );
9238
+ }
9239
+ return lines2;
9240
+ }
8992
9241
  var GITIGNORE_RECORD_DIR_LINE = ".uzys-agent-harness/";
8993
9242
  var GITIGNORE_RECORD_DIR_NOTE = "now ignores only the harness's runtime files \u2014 commit .uzys-agent-harness/ so teammates get the install record";
8994
9243
  function excludedKeyParts(f) {
@@ -9009,6 +9258,27 @@ function excludedKeyParts(f) {
9009
9258
  }
9010
9259
  return parts;
9011
9260
  }
9261
+ function catalogRemovedRows(items) {
9262
+ return items.map((e2) => {
9263
+ const parts = [
9264
+ e2.replacedBy === void 0 ? "\uCE74\uD0C8\uB85C\uADF8\uC5D0\uC11C \uBE60\uC84C\uB2E4" : `${e2.replacedBy} \uB85C \uBC14\uB00C\uC5C8\uB2E4`,
9265
+ e2.method === "skill" ? "\uAE50 \uAC83\uC740 \uADF8\uB300\uB85C \uB0A8\uACE0 update \uAC00 \uB354\uB294 \uAC31\uC2E0\uD558\uC9C0 \uC54A\uB294\uB2E4" : "\uAE50 \uAC83\uC740 \uADF8\uB300\uB85C \uB0A8\uB294\uB2E4"
9266
+ ];
9267
+ if (e2.replacedBy !== void 0) {
9268
+ if (e2.replacementReaches === false) {
9269
+ const next = EXTERNAL_ASSETS.find((a) => a.id === e2.replacedBy);
9270
+ const only = next ? assetCliSupport(next).map((cli) => CLI_DISPLAY_NAMES[cli]).join(" \xB7 ") : "\uB2E4\uB978 CLI";
9271
+ parts.push(`${e2.replacedBy} \uB294 ${only} \uC804\uC6A9\uC774\uB77C \uC774 \uC124\uCE58\uBCF8\uC758 CLI \uC5D0\uB294 \uAE54\uB9AC\uC9C0 \uC54A\uB294\uB2E4`);
9272
+ } else {
9273
+ parts.push(`\uC0C8 \uAC83\uC740 agent-harness install --with ${e2.replacedBy}`);
9274
+ }
9275
+ parts.push(`\uC61B \uAC83\uC744 \uCE58\uC6B0\uB824\uBA74 agent-harness uninstall --only ${e2.id}`);
9276
+ } else {
9277
+ parts.push(`\uCE58\uC6B0\uB824\uBA74 agent-harness uninstall --only ${e2.id}`);
9278
+ }
9279
+ return assetRow("skip", e2.id, parts.join(" \xB7 "));
9280
+ });
9281
+ }
9012
9282
  function excludedStillThereRows(items) {
9013
9283
  return items.map(
9014
9284
  (e2) => e2.catalog ? ` ${c.yellow(symbol.skip)} ${e2.id} \u2014 excluded, so the harness no longer updates it (still installed). To manage it again: run install (without --without ${e2.id}; add --with ${e2.id} if it is opt-in) \xB7 remove it: agent-harness uninstall --only ${e2.id}` : ` ${c.yellow(symbol.skip)} ${e2.id} \u2014 excluded (still on disk \u2014 an earlier install put it there; the harness does not delete it. Remove the file yourself, or run uninstall) \xB7 to manage it again: run install without --without ${e2.id}`
@@ -9347,7 +9617,7 @@ function executeSpec(requested, deps = {}) {
9347
9617
  return;
9348
9618
  }
9349
9619
  if (deps.mode === "update" && report.backup !== null) {
9350
- const liveClaude = join31(spec.projectDir, ".claude");
9620
+ const liveClaude = join32(spec.projectDir, ".claude");
9351
9621
  if (dirTreesIdentical(liveClaude, report.backup)) {
9352
9622
  try {
9353
9623
  rmSync4(report.backup, { recursive: true, force: true });
@@ -9435,7 +9705,7 @@ function registerInstallCommand(cli) {
9435
9705
  // src/commands/list.ts
9436
9706
  init_esm_shims();
9437
9707
  import { existsSync as existsSync24, readFileSync as readFileSync25 } from "fs";
9438
- import { join as join32, resolve as resolve3 } from "path";
9708
+ import { join as join33, resolve as resolve3 } from "path";
9439
9709
  function listAction(options = {}, deps = {}) {
9440
9710
  const log = deps.log ?? console.log;
9441
9711
  const err = deps.err ?? console.error;
@@ -9538,7 +9808,7 @@ function formatHarnessFileRows(log, projectDir) {
9538
9808
  const rows = [` ${c.dim(tops.length > 0 ? tops.join(" ") : "(none recorded)")}`];
9539
9809
  const rootMd = log.templates.rootClaudeMd;
9540
9810
  if (rootMd) {
9541
- const path = join32(projectDir, rootMd.path);
9811
+ const path = join33(projectDir, rootMd.path);
9542
9812
  const modified = existsSync24(path) && hashContent(readFileSync25(path, "utf8")) !== rootMd.sha256;
9543
9813
  rows.push(
9544
9814
  modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
@@ -9553,7 +9823,7 @@ var ROOT_FILE_CHANGE = {
9553
9823
  displaced: "\uBE44\uCF1C \uB460"
9554
9824
  };
9555
9825
  function formatRootFileRows(rootFiles, projectDir) {
9556
- const present2 = rootFiles.filter((f) => existsSync24(join32(projectDir, f.path)));
9826
+ const present2 = rootFiles.filter((f) => existsSync24(join33(projectDir, f.path)));
9557
9827
  const width = Math.max(0, ...present2.map((f) => f.path.length));
9558
9828
  return present2.map(
9559
9829
  (f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(ROOT_FILE_CHANGE[f.change])} ${c.dim(f.notes.join(" / "))}`
@@ -9583,7 +9853,7 @@ import {
9583
9853
  statSync as statSync4,
9584
9854
  writeFileSync as writeFileSync9
9585
9855
  } from "fs";
9586
- import { dirname as dirname6, join as join33, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
9856
+ import { dirname as dirname6, join as join34, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
9587
9857
 
9588
9858
  // src/uninstall-interactive.ts
9589
9859
  init_esm_shims();
@@ -10746,7 +11016,11 @@ function buildPageGroups(cats, initialSet, installedSet = /* @__PURE__ */ new Se
10746
11016
  );
10747
11017
  for (const a of catAssets) {
10748
11018
  const tier = assetTrustTier(a.id);
10749
- const badge = tier === "official" ? " \u2605 official" : tier === "experimental" ? " \u26A0 experimental (opt-in)" : "";
11019
+ const badge = tier === "official" ? " \u2605 official" : tier === "experimental" ? (
11020
+ // #713 — 트랙 기본으로 체크된 experimental(트랙 표의 railway 행)을 "opt-in" 이라 부르면 화면이 체크 상태와 어긋난다.
11021
+ // 이미 깔린 것은 `● installed` 가 말하므로 여기서는 이번 트랙의 기본만 가른다.
11022
+ initialSet.has(`asset:${a.id}`) && !installedSet.has(`asset:${a.id}`) ? " \u26A0 experimental (default on your tracks)" : " \u26A0 experimental (opt-in)"
11023
+ ) : "";
10750
11024
  items.push({
10751
11025
  value: `asset:${a.id}`,
10752
11026
  label: ` ${a.id} [${a.source}]${badge}${installedMark(`asset:${a.id}`)}`,
@@ -11199,7 +11473,7 @@ Proceed?`, initialValue: false });
11199
11473
 
11200
11474
  // src/commands/uninstall.ts
11201
11475
  function templateDirPath(projectDir, rel) {
11202
- return join33(projectDir, rel.replace(/\/+$/, ""));
11476
+ return join34(projectDir, rel.replace(/\/+$/, ""));
11203
11477
  }
11204
11478
  function outsideTemplateLink(projectDir, rel) {
11205
11479
  return outsideLinkAt(projectDir, rel.replace(/\/+$/, ""));
@@ -11422,7 +11696,7 @@ function unwritablePaths(ctx) {
11422
11696
  const project = ctx.targetAssets.filter((a) => a.scope === "project");
11423
11697
  if (project.some((a) => a.method === "npm")) need.add(".");
11424
11698
  const parentOf = (path) => {
11425
- const abs = join33(projectDir, path);
11699
+ const abs = join34(projectDir, path);
11426
11700
  if (!safeExists(abs)) return;
11427
11701
  const real = safeRealpath(abs);
11428
11702
  if (real !== null && isOutsideProject(projectDir, real)) return;
@@ -11433,7 +11707,7 @@ function unwritablePaths(ctx) {
11433
11707
  if (!ctx.keepTemplates) {
11434
11708
  const recordedShared = new Set((installLog.portions ?? []).map((p2) => p2.path));
11435
11709
  for (const p2 of ctx.sharedPaths) {
11436
- if (recordedShared.has(p2) && existsSync25(join33(projectDir, p2))) need.add(p2);
11710
+ if (recordedShared.has(p2) && existsSync25(join34(projectDir, p2))) need.add(p2);
11437
11711
  }
11438
11712
  for (const f of installLog.externalFiles ?? []) {
11439
11713
  if (!underAny(f.path, ctx.movedDirs)) parentOf(f.path);
@@ -11442,7 +11716,7 @@ function unwritablePaths(ctx) {
11442
11716
  }
11443
11717
  return [...need].filter((p2) => {
11444
11718
  try {
11445
- accessSync(join33(projectDir, p2), fsConstants.W_OK);
11719
+ accessSync(join34(projectDir, p2), fsConstants.W_OK);
11446
11720
  return false;
11447
11721
  } catch (e2) {
11448
11722
  return e2.code !== "ENOENT";
@@ -11526,7 +11800,7 @@ function settleLog(ctx, io) {
11526
11800
  return true;
11527
11801
  }
11528
11802
  } else {
11529
- io.rm(join33(projectDir, INSTALL_LOG_DIR));
11803
+ io.rm(join34(projectDir, INSTALL_LOG_DIR));
11530
11804
  if (outsideTemplateLink(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
11531
11805
  io.rm(legacyInstallLogPath(projectDir));
11532
11806
  }
@@ -11552,7 +11826,7 @@ function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles, har
11552
11826
  for (const rel of dirs) {
11553
11827
  const kept = outsideTemplateLink(projectDir, rel);
11554
11828
  if (kept) lines2.push(keptOutsideLinkLine(kept, installLog));
11555
- else if (existsSync25(join33(projectDir, rel))) lines2.push(MOVE_ASIDE_PREVIEW(rel));
11829
+ else if (existsSync25(join34(projectDir, rel))) lines2.push(MOVE_ASIDE_PREVIEW(rel));
11556
11830
  }
11557
11831
  if (dirs.length === 0) lines2.push(" \u25CB remove templates: (none)");
11558
11832
  const rootMd = installLog.templates.rootClaudeMd;
@@ -11610,7 +11884,7 @@ function backupArtifactAdvisoryLines(projectDir) {
11610
11884
  found.push(e2.name);
11611
11885
  }
11612
11886
  }
11613
- const agents = join33(projectDir, ".agents");
11887
+ const agents = join34(projectDir, ".agents");
11614
11888
  if (existsSync25(agents)) {
11615
11889
  for (const rel of listFilesRecursive(agents)) {
11616
11890
  if (stampRe.test(rel)) found.push(`.agents/${rel}`);
@@ -11626,11 +11900,11 @@ function backupArtifactAdvisoryLines(projectDir) {
11626
11900
  }
11627
11901
  function rootFileAdvisoryLines(rootFiles, projectDir, keepTemplates = false) {
11628
11902
  const header = keepTemplates ? "[LEFT] uninstall \uD6C4 \uB0A8\uB294 \uAC83 \u2014 templates \uBCF4\uC874(.claude/ \uB4F1) + \uB8E8\uD2B8 \uD30C\uC77C (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):" : "[ROOT] `.claude/` \uBC16\uC5D0 \uB0A8\uB294 \uAC83 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):";
11629
- const present2 = rootFiles.filter((f) => existsSync25(join33(projectDir, f.path)));
11903
+ const present2 = rootFiles.filter((f) => existsSync25(join34(projectDir, f.path)));
11630
11904
  if (present2.length === 0) return [];
11631
11905
  const outside = /* @__PURE__ */ new Map();
11632
11906
  const inside = present2.filter((f) => {
11633
- const o = outsideProjectTarget(projectDir, join33(projectDir, f.path));
11907
+ const o = outsideProjectTarget(projectDir, join34(projectDir, f.path));
11634
11908
  if (o === null) return true;
11635
11909
  const group = outside.get(o.link) ?? { link: o, files: [] };
11636
11910
  group.files.push(f.path);
@@ -11669,7 +11943,7 @@ function rootFileMeaning(f, projectDir) {
11669
11943
  case "displaced": {
11670
11944
  const backup = f.notes[0];
11671
11945
  if (backup === void 0) return "\uD558\uB124\uC2A4 \uD310\uACFC \uAC19\uC740 \uC124\uCE58\uC790 \uD30C\uC77C\uC774 \uC788\uB358 \uC790\uB9AC (\uADF8\uB300\uB85C \uB450\uC5C8\uB2E4)";
11672
- return existsSync25(join33(projectDir, backup)) ? `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC740 ${backup} \uC5D0 \uC788\uB2E4` : `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC758 \uBC31\uC5C5(${backup})\uC774 \uB354\uB294 \uC5C6\uB2E4`;
11946
+ return existsSync25(join34(projectDir, backup)) ? `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC740 ${backup} \uC5D0 \uC788\uB2E4` : `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC758 \uBC31\uC5C5(${backup})\uC774 \uB354\uB294 \uC5C6\uB2E4`;
11673
11947
  }
11674
11948
  }
11675
11949
  }
@@ -11748,7 +12022,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
11748
12022
  for (const dir of plan.dirs) {
11749
12023
  const kept = outsideTemplateLink(projectDir, dir);
11750
12024
  if (kept) lines2.push(keptOutsideLinkLine(kept, installLog));
11751
- else if (existsSync25(join33(projectDir, dir))) lines2.push(MOVE_ASIDE_PREVIEW(dir));
12025
+ else if (existsSync25(join34(projectDir, dir))) lines2.push(MOVE_ASIDE_PREVIEW(dir));
11752
12026
  }
11753
12027
  lines2.push(...previewExternalLines(plan.scoped, projectDir, harnessRoot));
11754
12028
  lines2.push(
@@ -11758,7 +12032,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
11758
12032
  )
11759
12033
  );
11760
12034
  const rootMd = plan.anchor ? installLog.templates.rootClaudeMd : void 0;
11761
- if (rootMd && existsSync25(join33(projectDir, rootMd.path))) {
12035
+ if (rootMd && existsSync25(join34(projectDir, rootMd.path))) {
11762
12036
  lines2.push(
11763
12037
  rootClaudeMdModified(installLog, projectDir) ? ` \u25CB keep ${rootMd.path} (modified since install \u2014 preserved)` : ` \u25CB remove ${rootMd.path}`
11764
12038
  );
@@ -11781,7 +12055,7 @@ function agentsMdRerenderSet(target, remaining, log, projectDir) {
11781
12055
  const set = agentsMdClis(remaining);
11782
12056
  if (set.length === 0) return null;
11783
12057
  const recorded2 = (log.externalFiles ?? []).some((f) => f.path === AGENTS_MD2) || (log.portions ?? []).some((p2) => p2.path === AGENTS_MD2);
11784
- if (!recorded2 || !existsSync25(join33(projectDir, AGENTS_MD2))) return null;
12058
+ if (!recorded2 || !existsSync25(join34(projectDir, AGENTS_MD2))) return null;
11785
12059
  return set;
11786
12060
  }
11787
12061
  function rerenderAgentsMd(ctx, set) {
@@ -11825,14 +12099,14 @@ function rerenderLines(r) {
11825
12099
  function removeHarnessAnchor(ctx, io) {
11826
12100
  const { installLog, projectDir } = ctx;
11827
12101
  const rootMd = installLog.templates.rootClaudeMd;
11828
- if (!rootMd || !existsSync25(join33(projectDir, rootMd.path))) return false;
12102
+ if (!rootMd || !existsSync25(join34(projectDir, rootMd.path))) return false;
11829
12103
  if (rootClaudeMdModified(installLog, projectDir)) {
11830
12104
  io.log(
11831
12105
  ` ${c.yellow("\u2298")} ${rootMd.path} kept \u2014 modified since install. Remove manually if intended.`
11832
12106
  );
11833
12107
  return false;
11834
12108
  }
11835
- io.rm(join33(projectDir, rootMd.path));
12109
+ io.rm(join34(projectDir, rootMd.path));
11836
12110
  io.log(` ${status.success(`${rootMd.path} removed`)}`);
11837
12111
  return true;
11838
12112
  }
@@ -11890,7 +12164,7 @@ function removeCliAction(ctx, io) {
11890
12164
  io.log(keptOutsideLinkLine(kept, installLog));
11891
12165
  continue;
11892
12166
  }
11893
- if (!existsSync25(join33(projectDir, dir))) continue;
12167
+ if (!existsSync25(join34(projectDir, dir))) continue;
11894
12168
  const backup = io.moveAside(templateDirPath(projectDir, dir));
11895
12169
  if (backup) io.log(movedAsideLine(projectDir, dir, backup));
11896
12170
  }
@@ -11931,7 +12205,7 @@ var SKILLS_LOCK = "skills-lock.json";
11931
12205
  function withSkillsLock(rootFiles, assets, projectDir) {
11932
12206
  const out = [...rootFiles];
11933
12207
  const skills = assets.some((a) => a.method === "skill" && a.scope === "project");
11934
- if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync25(join33(projectDir, SKILLS_LOCK))) {
12208
+ if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync25(join34(projectDir, SKILLS_LOCK))) {
11935
12209
  out.push({ path: SKILLS_LOCK, change: "advisory", notes: [] });
11936
12210
  }
11937
12211
  return out;
@@ -11949,9 +12223,9 @@ function remnantFor(path, log, harnessRoot) {
11949
12223
  } catch {
11950
12224
  return [];
11951
12225
  }
11952
- const render = new Map(Object.entries(servers).map(([n, v2]) => [`mcpServers.${n}`, v2]));
11953
- const present2 = ADAPTERS["json-keys"].read(disk, render.keys()) ?? /* @__PURE__ */ new Map();
11954
- return [...present2].filter(([k2, sha]) => sha === jsonSha(render.get(k2))).map(([k2]) => k2.slice("mcpServers.".length));
12226
+ const known = knownServerValues(servers, (v2) => toClaudeServer(v2));
12227
+ const present2 = ADAPTERS["json-keys"].read(disk, known.keys()) ?? /* @__PURE__ */ new Map();
12228
+ return [...present2].filter(([k2, sha]) => known.get(k2)?.has(sha) === true).map(([k2]) => k2.slice("mcpServers.".length));
11955
12229
  };
11956
12230
  }
11957
12231
  if (path === AGENTS_MD2) {
@@ -11960,18 +12234,32 @@ function remnantFor(path, log, harnessRoot) {
11960
12234
  }
11961
12235
  if (!clis.includes("opencode")) return () => [];
11962
12236
  return (disk) => {
11963
- let render;
12237
+ let known;
11964
12238
  try {
11965
- render = renderOpencodeMcp(
11966
- renderEverySelectableMcp(harnessRoot, log.spec.tracks.filter(isTrack))
12239
+ known = knownServerValues(
12240
+ renderEverySelectableMcp(harnessRoot, log.spec.tracks.filter(isTrack)).mcpServers,
12241
+ (v2) => [...renderOpencodeMcp({ mcpServers: { x: v2 } }).values()][0],
12242
+ "mcp."
11967
12243
  );
11968
12244
  } catch {
11969
12245
  return [];
11970
12246
  }
11971
- const present2 = ADAPTERS["json-keys"].read(disk, render.keys()) ?? /* @__PURE__ */ new Map();
11972
- return [...present2].filter(([k2, sha]) => sha === jsonSha(render.get(k2))).map(([k2]) => k2);
12247
+ const present2 = ADAPTERS["json-keys"].read(disk, known.keys()) ?? /* @__PURE__ */ new Map();
12248
+ return [...present2].filter(([k2, sha]) => known.get(k2)?.has(sha) === true).map(([k2]) => k2);
11973
12249
  };
11974
12250
  }
12251
+ function knownServerValues(servers, form, prefix = "mcpServers.") {
12252
+ const out = /* @__PURE__ */ new Map();
12253
+ const add = (name, v2) => {
12254
+ const key = `${prefix}${name}`;
12255
+ const set = out.get(key) ?? /* @__PURE__ */ new Set();
12256
+ set.add(jsonSha(form(v2)));
12257
+ out.set(key, set);
12258
+ };
12259
+ for (const [name, v2] of Object.entries(servers)) add(name, v2);
12260
+ for (const p2 of PRIOR_MCP_SERVERS) add(p2.name, p2.value);
12261
+ return out;
12262
+ }
11975
12263
  function remnantLine(path) {
11976
12264
  if (path === ".mcp.json") {
11977
12265
  return (names) => `may still hold the harness's MCP servers (not on record): ${names.join(" \xB7 ")} \u2014 delete them by hand if they are not yours`;
@@ -12055,7 +12343,7 @@ function noReverseHints(asset, ctx) {
12055
12343
  }
12056
12344
  if (asset.method !== "skill") return [];
12057
12345
  const name = asset.detail.skill;
12058
- const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => !underAny(p2, ctx.movedDirs) && existsSync25(join33(ctx.projectDir, p2)));
12346
+ const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => !underAny(p2, ctx.movedDirs) && existsSync25(join34(ctx.projectDir, p2)));
12059
12347
  return [
12060
12348
  left.length > 0 ? `files it put down are not on record (installed by an older version) \u2014 left: ${left.join(" \xB7 ")} (delete by hand if the harness installed it)` : "files it put down are not on record (installed by an older version) \u2014 nothing removed"
12061
12349
  ];
@@ -12162,14 +12450,14 @@ function removeTemplates(log, projectDir, io, harnessRoot) {
12162
12450
  if (rootMd) {
12163
12451
  if (rootClaudeMdModified(log, projectDir))
12164
12452
  return { rootClaudeMdKept: true, importStripped, external, moved, keptLinks };
12165
- rm(join33(projectDir, rootMd.path));
12453
+ rm(join34(projectDir, rootMd.path));
12166
12454
  }
12167
12455
  return { rootClaudeMdKept: false, importStripped, external, moved, keptLinks };
12168
12456
  }
12169
12457
  var AGENTS_MD2 = "AGENTS.md";
12170
12458
  function readAgentsMdTemplate(harnessRoot) {
12171
12459
  try {
12172
- return readFileSync26(join33(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
12460
+ return readFileSync26(join34(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
12173
12461
  } catch {
12174
12462
  return null;
12175
12463
  }
@@ -12180,7 +12468,7 @@ function recoverMovedSymlinkPortions(log, projectDir, dirs) {
12180
12468
  for (const path of paths) {
12181
12469
  if (path.startsWith(".agents/skills/")) continue;
12182
12470
  if (!dirs.some((d2) => path.startsWith(d2))) continue;
12183
- const abs = join33(projectDir, path);
12471
+ const abs = join34(projectDir, path);
12184
12472
  if (!safeIsSymlink(abs)) continue;
12185
12473
  const target = safeRealpath(abs);
12186
12474
  if (target !== null && isOutsideProject(projectDir, target)) {
@@ -12209,7 +12497,7 @@ function recoverMovedSymlinkTargets(log, projectDir, dirs, rm) {
12209
12497
  for (const { path, sha256 } of log.externalFiles ?? []) {
12210
12498
  if (path.startsWith(".agents/skills/")) continue;
12211
12499
  if (!dirs.some((d2) => path.startsWith(d2))) continue;
12212
- const abs = join33(projectDir, path);
12500
+ const abs = join34(projectDir, path);
12213
12501
  if (!safeIsSymlink(abs)) continue;
12214
12502
  let target;
12215
12503
  try {
@@ -12271,7 +12559,7 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
12271
12559
  const unjudged = [];
12272
12560
  const outside = [];
12273
12561
  for (const { path, sha256 } of log.externalFiles ?? []) {
12274
- const abs = join33(projectDir, path);
12562
+ const abs = join34(projectDir, path);
12275
12563
  if (!existsSync25(abs)) continue;
12276
12564
  let current;
12277
12565
  let effective = abs;
@@ -12320,8 +12608,8 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
12320
12608
  }
12321
12609
  removed.push(path);
12322
12610
  }
12323
- for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join33(projectDir, path)));
12324
- const decisions = join33(projectDir, "docs", "decisions");
12611
+ for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join34(projectDir, path)));
12612
+ const decisions = join34(projectDir, "docs", "decisions");
12325
12613
  try {
12326
12614
  if (existsSync25(decisions) && readdirSync6(decisions).length === 0)
12327
12615
  rmSync5(decisions, { recursive: true });
@@ -12334,7 +12622,7 @@ function removeToolFiles(files, projectDir, rm) {
12334
12622
  const removed = [];
12335
12623
  let failed = 0;
12336
12624
  for (const { path, sha256 } of files) {
12337
- const abs = join33(projectDir, path);
12625
+ const abs = join34(projectDir, path);
12338
12626
  let isLink;
12339
12627
  try {
12340
12628
  isLink = lstatSync5(abs).isSymbolicLink();
@@ -12362,7 +12650,7 @@ function removeToolFiles(files, projectDir, rm) {
12362
12650
  notes.push(`left ${path} \u2014 ${e2 instanceof Error ? e2.message : String(e2)}`);
12363
12651
  }
12364
12652
  }
12365
- for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join33(projectDir, path)));
12653
+ for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join34(projectDir, path)));
12366
12654
  return failed > 0 ? { ok: false, message: `${failed} file(s) could not be removed`, notes } : { ok: true, notes };
12367
12655
  }
12368
12656
  function pruneEmptyDirsUpward(projectDir, startDir) {
@@ -12384,8 +12672,8 @@ function stripRootImport(projectDir) {
12384
12672
  const stripped = current === null ? null : stripHarnessImport(current);
12385
12673
  if (stripped === null) return false;
12386
12674
  try {
12387
- backupIfLossyUtf8(join33(projectDir, "CLAUDE.md"));
12388
- writeFileSync9(join33(projectDir, "CLAUDE.md"), stripped, "utf8");
12675
+ backupIfLossyUtf8(join34(projectDir, "CLAUDE.md"));
12676
+ writeFileSync9(join34(projectDir, "CLAUDE.md"), stripped, "utf8");
12389
12677
  return true;
12390
12678
  } catch {
12391
12679
  return false;
@@ -12395,7 +12683,7 @@ function previewExternalLines(installLog, projectDir, harnessRoot, movedDirs = [
12395
12683
  const lines2 = [];
12396
12684
  let removable = 0;
12397
12685
  for (const { path, sha256 } of installLog.externalFiles ?? []) {
12398
- const abs = join33(projectDir, path);
12686
+ const abs = join34(projectDir, path);
12399
12687
  const underMoved = underAny(path, movedDirs);
12400
12688
  const isLink = !underMoved || existsSync25(abs) ? safeIsSymlink(abs) : false;
12401
12689
  if (underMoved && !isLink) continue;
@@ -12479,7 +12767,7 @@ function hasRootImport(projectDir) {
12479
12767
  return current !== null && stripHarnessImport(current) !== null;
12480
12768
  }
12481
12769
  function readRootClaudeMd(projectDir) {
12482
- const path = join33(projectDir, "CLAUDE.md");
12770
+ const path = join34(projectDir, "CLAUDE.md");
12483
12771
  if (!existsSync25(path)) return null;
12484
12772
  try {
12485
12773
  return readFileSync26(path, "utf8");
@@ -12490,7 +12778,7 @@ function readRootClaudeMd(projectDir) {
12490
12778
  function rootClaudeMdModified(log, projectDir) {
12491
12779
  const rootMd = log.templates.rootClaudeMd;
12492
12780
  if (!rootMd) return false;
12493
- const path = join33(projectDir, rootMd.path);
12781
+ const path = join34(projectDir, rootMd.path);
12494
12782
  if (!existsSync25(path)) return false;
12495
12783
  return hashContent(readFileSync26(path, "utf8")) !== rootMd.sha256;
12496
12784
  }
@@ -13032,7 +13320,7 @@ function computeUserOverride(tracks, assetIds) {
13032
13320
  const recommended = new Set(wizardRecommended(tracks));
13033
13321
  const selected = new Set(assetIds);
13034
13322
  const forceExclude = [...recommended].filter((id) => !selected.has(id)).sort();
13035
- const forceInclude = [...selected].filter((id) => !recommended.has(id)).sort();
13323
+ const forceInclude = [...selected].filter((id) => !recommended.has(id) && EXTERNAL_ASSETS.some((a) => a.id === id)).sort();
13036
13324
  if (forceInclude.length === 0 && forceExclude.length === 0) return void 0;
13037
13325
  return { forceInclude, forceExclude };
13038
13326
  }