@uzysjung/agent-harness 26.171.0 → 26.173.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.
package/dist/index.js CHANGED
@@ -101,7 +101,7 @@ import {
101
101
  targetsInclude,
102
102
  upsertHarnessImport,
103
103
  writeInstallLog
104
- } from "./chunk-U4X4Z333.js";
104
+ } from "./chunk-JABFWW6X.js";
105
105
 
106
106
  // node_modules/sisteransi/src/index.js
107
107
  var require_src = __commonJS({
@@ -3713,8 +3713,8 @@ function padDisplay(s, width) {
3713
3713
 
3714
3714
  // src/install-writes.ts
3715
3715
  init_esm_shims();
3716
- import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync3 } from "fs";
3717
- import { dirname, join as join7, relative, sep } from "path";
3716
+ import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
3717
+ import { dirname, join as join8, relative, sep } from "path";
3718
3718
 
3719
3719
  // src/env-files.ts
3720
3720
  init_esm_shims();
@@ -3804,7 +3804,8 @@ var LINES = {
3804
3804
  newInRelease: "wrote (new in this release)",
3805
3805
  refreshed: "refreshed",
3806
3806
  sameAsHarness: "kept \u2014 already the same as the harness version (left yours in place)",
3807
- hadFile: "had a file with this name \u2014 saved as <file>.backup-<time>",
3807
+ // 비켜 둔 설치자 파일은 하네스가 그 자리를 떠날 때(uninstall · 회수) 되돌린다(`displaced` · #782) — 그 약속을 같은 줄에
3808
+ hadFile: "had a file with this name \u2014 saved as <file>.backup-<time> \u2014 yours, put back on uninstall",
3808
3809
  noChecksum: "no checksum on record \u2014 saved a copy once",
3809
3810
  edited: "you edited it \u2014 saved as <file>.backup-<time>",
3810
3811
  removed: "removed",
@@ -3817,6 +3818,9 @@ var LINES = {
3817
3818
  scaffoldKept: "kept \u2014 already there (yours)",
3818
3819
  leftForYou: "left for you (yours now)"
3819
3820
  };
3821
+ function withBackup(line, backup) {
3822
+ return line.replace("<file>.backup-<time>", backup);
3823
+ }
3820
3824
  function unreadable(adapter, op) {
3821
3825
  const what = op === "write" ? "harness part not added" : "harness part not removed";
3822
3826
  return `could not read it (${ADAPTERS[adapter].unreadable}) \u2014 ${what}`;
@@ -3935,7 +3939,8 @@ var PRIOR_MCP_SERVERS = [
3935
3939
  // #766 — `@supabase/mcp-server` 는 npm 에 없는 이름이라(404) 서버가 뜬 적이 없다
3936
3940
  name: "supabase",
3937
3941
  value: { type: "stdio", command: "npx", args: ["-y", "@supabase/mcp-server"] },
3938
- updated: "now Supabase's remote server \u2014 sign in on first use, see NEXT"
3942
+ updated: "now Supabase's remote server \u2014 sign in on first use, see NEXT",
3943
+ dead: `runs @supabase/mcp-server, which is not on npm \u2014 this server never started. To use Supabase's official remote server, set it to { "type": "http", "url": "https://mcp.supabase.com/mcp" } (signs in on first use)`
3939
3944
  },
3940
3945
  {
3941
3946
  name: "chrome-devtools",
@@ -4116,7 +4121,7 @@ function recordedMcpAssetIds(log) {
4116
4121
  // src/recorded.ts
4117
4122
  init_esm_shims();
4118
4123
  import { existsSync as existsSync5 } from "fs";
4119
- import { join as join6 } from "path";
4124
+ import { join as join7 } from "path";
4120
4125
 
4121
4126
  // src/cli-ownership.ts
4122
4127
  init_esm_shims();
@@ -4171,6 +4176,42 @@ function removableFor(cli, remaining) {
4171
4176
  return { exclusive, sharedNowUnowned };
4172
4177
  }
4173
4178
 
4179
+ // src/foreign-slot.ts
4180
+ init_esm_shims();
4181
+ import { lstatSync, realpathSync, statSync } from "fs";
4182
+ import { join as join6 } from "path";
4183
+ var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
4184
+ function occupiedByNonDirectory(path) {
4185
+ const stat = lstatSync(path, { throwIfNoEntry: false });
4186
+ return stat !== void 0 && !stat.isDirectory();
4187
+ }
4188
+ function linksToProjectSharedSkill(projectDir, id) {
4189
+ const slot = join6(projectDir, ".claude/skills", id);
4190
+ if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
4191
+ try {
4192
+ const real = realpathSync(slot);
4193
+ return real === join6(realpathSync(projectDir), ".agents/skills", id) && statSync(real).isDirectory();
4194
+ } catch {
4195
+ return false;
4196
+ }
4197
+ }
4198
+ function foreignOwnedTarget(projectDir, rel) {
4199
+ const slot = SKILL_SLOT.exec(rel)?.[1];
4200
+ if (slot === void 0) return null;
4201
+ if (occupiedByNonDirectory(join6(projectDir, slot))) return slot;
4202
+ if (rel !== slot) {
4203
+ const parts = rel.slice(slot.length + 1).split("/");
4204
+ let cursor = slot;
4205
+ for (const part of parts.slice(0, -1)) {
4206
+ cursor = `${cursor}/${part}`;
4207
+ if (occupiedByNonDirectory(join6(projectDir, cursor))) return cursor;
4208
+ }
4209
+ const leaf = lstatSync(join6(projectDir, rel), { throwIfNoEntry: false });
4210
+ if (leaf !== void 0 && !leaf.isFile()) return rel;
4211
+ }
4212
+ return null;
4213
+ }
4214
+
4174
4215
  // src/recorded.ts
4175
4216
  var CLAUDE_DIR = ".claude/";
4176
4217
  var ADVISORY_PATHS = [".env.example", ".github/workflows/", "skills-lock.json"];
@@ -4190,6 +4231,15 @@ function ownersOf(path) {
4190
4231
  )
4191
4232
  );
4192
4233
  }
4234
+ function usersOf(path, projectDir) {
4235
+ const owners = ownersOf(path);
4236
+ const id = /^\.agents\/skills\/([^/]+)\//.exec(path)?.[1];
4237
+ const linked = id !== void 0 && linksToProjectSharedSkill(projectDir, id);
4238
+ return linked && !owners.includes("claude") ? [...owners, "claude"] : owners;
4239
+ }
4240
+ function bundlePath(path) {
4241
+ return path.startsWith(".agents/skills/") ? `${SKILLS_DIR}${path.slice(".agents/skills/".length)}` : path;
4242
+ }
4193
4243
  function isRecordedTrackTarget(log, path) {
4194
4244
  return recordedTrackTargets(log)(path);
4195
4245
  }
@@ -4199,9 +4249,9 @@ function recordedTrackTargets(log) {
4199
4249
  selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS
4200
4250
  };
4201
4251
  const entries = buildManifest(spec).filter((e2) => e2.applies(spec));
4202
- const templates = join6(resolveBundleRoot(), "templates");
4252
+ const templates = join7(resolveBundleRoot(), "templates");
4203
4253
  return (path) => entries.some(
4204
- (e2) => e2.type === "dir" ? path.startsWith(`${e2.target}/`) && existsSync5(join6(templates, e2.source, path.slice(e2.target.length + 1))) : e2.target === path
4254
+ (e2) => e2.type === "dir" ? path.startsWith(`${e2.target}/`) && existsSync5(join7(templates, e2.source, path.slice(e2.target.length + 1))) : e2.target === path
4205
4255
  );
4206
4256
  }
4207
4257
  function recordedTrackSpots(log) {
@@ -4209,13 +4259,13 @@ function recordedTrackSpots(log) {
4209
4259
  tracks: log.spec.tracks.filter(isTrack),
4210
4260
  selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS
4211
4261
  };
4212
- const templates = join6(resolveBundleRoot(), "templates");
4262
+ const templates = join7(resolveBundleRoot(), "templates");
4213
4263
  const out = [];
4214
4264
  for (const e2 of buildManifest(spec)) {
4215
4265
  if (!e2.applies(spec) || !e2.target.startsWith(CLAUDE_DIR)) continue;
4216
4266
  if (e2.type === "file") out.push(e2.target);
4217
4267
  else
4218
- for (const rel of listFilesRecursive(join6(templates, e2.source)))
4268
+ for (const rel of listFilesRecursive(join7(templates, e2.source)))
4219
4269
  out.push(`${e2.target}/${rel}`);
4220
4270
  }
4221
4271
  for (const p2 of RETIRED_PATHS) out.push(`${CLAUDE_DIR}${p2}`);
@@ -4294,7 +4344,6 @@ function isScaffoldPath(path) {
4294
4344
  // src/install-writes.ts
4295
4345
  var CLAUDE_DIR2 = ".claude/";
4296
4346
  var SKILLS_DIR2 = ".claude/skills/";
4297
- var BACKUP_PLACEHOLDER = "<file>.backup-<time>";
4298
4347
  function createInstallWriter(args) {
4299
4348
  const { projectDir, previousLog, excluded } = args;
4300
4349
  const now = args.now ?? /* @__PURE__ */ new Date();
@@ -4319,16 +4368,17 @@ function createInstallWriter(args) {
4319
4368
  else external.set(path, sha256);
4320
4369
  }
4321
4370
  function harness(path, from, opts = {}) {
4322
- const abs = join7(projectDir, path);
4371
+ const abs = join8(projectDir, path);
4323
4372
  if (outside.skip(abs)) return null;
4324
4373
  const next = "source" in from ? readFileSync4(from.source, "utf8") : from.content;
4325
4374
  const disk = existsSync6(abs) ? readFileSync4(abs, "utf8") : null;
4326
4375
  const rec = recorded(previousLog, path, opts);
4327
4376
  const diskSha = rec.state === "no-sha" && disk !== null ? hashContent(disk) : null;
4377
+ const judgedRec = diskSha !== null && isLegacyHarnessFile(path, diskSha) ? { state: "sha", sha256: diskSha } : rec;
4328
4378
  const j4 = judge({
4329
4379
  op: "write",
4330
4380
  kind: "harness",
4331
- rec: diskSha !== null && isLegacyHarnessFile(path, diskSha) ? { state: "sha", sha256: diskSha } : rec,
4381
+ rec: judgedRec,
4332
4382
  disk,
4333
4383
  next,
4334
4384
  ...args.run === void 0 ? {} : { run: args.run }
@@ -4373,11 +4423,19 @@ function createInstallWriter(args) {
4373
4423
  default:
4374
4424
  throw new Error(`install: '${j4.record}' is not a write record (${path})`);
4375
4425
  }
4376
- const line = backup === void 0 ? j4.line : j4.line.replace(BACKUP_PLACEHOLDER, backup);
4426
+ const line = backup === void 0 ? j4.line : withBackup(j4.line, backup);
4377
4427
  const backupPair = backup === void 0 || backupAbs === void 0 ? {} : { backup, backupAbs };
4378
- writes.push({ path, verdict: j4.verdict, record: j4.record, line, ...backupPair });
4428
+ const state = judgedRec.state;
4429
+ writes.push({
4430
+ path,
4431
+ verdict: j4.verdict,
4432
+ record: j4.record,
4433
+ recorded: state,
4434
+ line,
4435
+ ...backupPair
4436
+ });
4379
4437
  if (j4.line === "" || j4.verdict === "create" || j4.verdict === "overwrite") return null;
4380
- const out = { path, verdict: j4.verdict, line, ...backupPair };
4438
+ const out = { path, verdict: j4.verdict, recorded: state, line, ...backupPair };
4381
4439
  judged.push(out);
4382
4440
  return out;
4383
4441
  }
@@ -4385,7 +4443,7 @@ function createInstallWriter(args) {
4385
4443
  const file = SHARED_FILES[path];
4386
4444
  if (file === void 0) throw new Error(`install: ${path} is not a shared file`);
4387
4445
  const adapter = ADAPTERS[file.adapter];
4388
- const abs = join7(projectDir, path);
4446
+ const abs = join8(projectDir, path);
4389
4447
  const disk = existsSync6(abs) ? readFileSync4(abs, "utf8") : null;
4390
4448
  const own = new Map(
4391
4449
  (previousLog?.portions ?? []).filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
@@ -4567,9 +4625,9 @@ function retiredClaims(previous, projectDir, kept) {
4567
4625
  const out = [];
4568
4626
  for (const p2 of RETIRED_PATHS) {
4569
4627
  if (have.has(p2) || recorded(previous, `${CLAUDE_DIR2}${p2}`).state === "none") continue;
4570
- const abs = join7(projectDir, CLAUDE_DIR2, p2);
4628
+ const abs = join8(projectDir, CLAUDE_DIR2, p2);
4571
4629
  try {
4572
- if (!statSync(abs).isFile()) continue;
4630
+ if (!statSync2(abs).isFile()) continue;
4573
4631
  out.push({ path: p2, sha256: hashContent(readFileSync4(abs, "utf8")) });
4574
4632
  } catch {
4575
4633
  }
@@ -4674,7 +4732,7 @@ function legacySettingsSeed(text, render, projectDir, opts = {}) {
4674
4732
  } catch {
4675
4733
  return seed;
4676
4734
  }
4677
- const claudeDir = join7(projectDir, ".claude");
4735
+ const claudeDir = join8(projectDir, ".claude");
4678
4736
  const candidates = new Set([...render.keys()].filter((k2) => k2.startsWith("hooks.")));
4679
4737
  const legacyLines = /* @__PURE__ */ new Set();
4680
4738
  const hooks = isObject2(root) && isObject2(root.hooks) ? root.hooks : {};
@@ -4790,13 +4848,13 @@ function legacyGitignoreSeed(text, render, previous) {
4790
4848
 
4791
4849
  // src/installer.ts
4792
4850
  init_esm_shims();
4793
- import { chmodSync as chmodSync3, existsSync as existsSync22, readdirSync as readdirSync5, readFileSync as readFileSync22, writeFileSync as writeFileSync8 } from "fs";
4794
- import { basename as basename4, join as join31, relative as relative5, resolve, sep as sep6 } from "path";
4851
+ import { chmodSync as chmodSync3, existsSync as existsSync22, readdirSync as readdirSync5, readFileSync as readFileSync23, writeFileSync as writeFileSync8 } from "fs";
4852
+ import { basename as basename4, join as join31, relative as relative6, resolve, sep as sep7 } from "path";
4795
4853
 
4796
4854
  // src/anchor-seed.ts
4797
4855
  init_esm_shims();
4798
4856
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
4799
- import { join as join8 } from "path";
4857
+ import { join as join9 } from "path";
4800
4858
 
4801
4859
  // src/agents-md-render.ts
4802
4860
  init_esm_shims();
@@ -4903,7 +4961,7 @@ function compose(body, into) {
4903
4961
  ${trimmed.join("\n")}`;
4904
4962
  }
4905
4963
  function seedAgentsMdProjectContext(projectDir) {
4906
- const root = readIfExists(join8(projectDir, "CLAUDE.md"));
4964
+ const root = readIfExists(join9(projectDir, "CLAUDE.md"));
4907
4965
  if (root === null) return null;
4908
4966
  const withoutImport = stripHarnessImport(root) ?? root;
4909
4967
  const body = dropHarnessQuoteBlocks(dropTitle(withoutImport.split("\n")));
@@ -4911,9 +4969,9 @@ function seedAgentsMdProjectContext(projectDir) {
4911
4969
  return compose(body, "agents-md");
4912
4970
  }
4913
4971
  function seedRootClaudeProjectContext(projectDir, harnessRoot) {
4914
- const agents = readIfExists(join8(projectDir, "AGENTS.md"));
4972
+ const agents = readIfExists(join9(projectDir, "AGENTS.md"));
4915
4973
  if (agents === null) return null;
4916
- const template = readIfExists(join8(harnessRoot, AGENTS_MD_TEMPLATE));
4974
+ const template = readIfExists(join9(harnessRoot, AGENTS_MD_TEMPLATE));
4917
4975
  if (template === null) return null;
4918
4976
  const installer = projectContextInstallerLines({ existing: agents, template });
4919
4977
  if (installer === null) return null;
@@ -4925,7 +4983,7 @@ function seedRootClaudeProjectContext(projectDir, harnessRoot) {
4925
4983
  // src/ci-scaffold.ts
4926
4984
  init_esm_shims();
4927
4985
  import { existsSync as existsSync8 } from "fs";
4928
- import { join as join9 } from "path";
4986
+ import { join as join10 } from "path";
4929
4987
  var PYTHON_TRACKS = "data|csr-fastapi|full";
4930
4988
  var VARIANTS = [
4931
4989
  {
@@ -4949,12 +5007,12 @@ function installCiScaffold(ctx) {
4949
5007
  for (const v2 of VARIANTS) {
4950
5008
  if (!v2.applies(ctx.tracks)) continue;
4951
5009
  const relTarget = `.github/workflows/${v2.target}`;
4952
- const target = join9(ctx.projectDir, relTarget);
5010
+ const target = join10(ctx.projectDir, relTarget);
4953
5011
  if (existsSync8(target)) {
4954
5012
  report.skippedExisting.push(relTarget);
4955
5013
  continue;
4956
5014
  }
4957
- copyFile(join9(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
5015
+ copyFile(join10(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
4958
5016
  report.written.push(relTarget);
4959
5017
  }
4960
5018
  return report;
@@ -4972,7 +5030,7 @@ import { basename, join as join14 } from "path";
4972
5030
 
4973
5031
  // src/agents-skill-targets.ts
4974
5032
  init_esm_shims();
4975
- import { join as join10, sep as sep2 } from "path";
5033
+ import { join as join11, sep as sep2 } from "path";
4976
5034
  function bundledSkillTargets(projectDir, log = readInstallLog(projectDir)) {
4977
5035
  if (log === null) return [];
4978
5036
  const tracks = log.spec.tracks.filter(
@@ -4986,7 +5044,7 @@ function bundledSkillTargets(projectDir, log = readInstallLog(projectDir)) {
4986
5044
  }
4987
5045
  function allowSkillCreation(writer, projectDir, ids) {
4988
5046
  if (ids.length === 0) return writer;
4989
- const roots = ids.map((id) => `${join10(projectDir, ".agents", "skills", id)}${sep2}`);
5047
+ const roots = ids.map((id) => `${join11(projectDir, ".agents", "skills", id)}${sep2}`);
4990
5048
  return {
4991
5049
  write(absPath, content, opts) {
4992
5050
  const inTarget = roots.some((root) => absPath.startsWith(root));
@@ -5009,48 +5067,11 @@ function agentsSkillSlot(params) {
5009
5067
  init_esm_shims();
5010
5068
  import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
5011
5069
  import { dirname as dirname2, relative as relative2, sep as sep3 } from "path";
5012
-
5013
- // src/foreign-slot.ts
5014
- init_esm_shims();
5015
- import { lstatSync, realpathSync, statSync as statSync2 } from "fs";
5016
- import { join as join11 } from "path";
5017
- var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
5018
- function occupiedByNonDirectory(path) {
5019
- const stat = lstatSync(path, { throwIfNoEntry: false });
5020
- return stat !== void 0 && !stat.isDirectory();
5021
- }
5022
- function linksToProjectSharedSkill(projectDir, id) {
5023
- const slot = join11(projectDir, ".claude/skills", id);
5024
- if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
5025
- try {
5026
- const real = realpathSync(slot);
5027
- return real === join11(realpathSync(projectDir), ".agents/skills", id) && statSync2(real).isDirectory();
5028
- } catch {
5029
- return false;
5030
- }
5031
- }
5032
- function foreignOwnedTarget(projectDir, rel) {
5033
- const slot = SKILL_SLOT.exec(rel)?.[1];
5034
- if (slot === void 0) return null;
5035
- if (occupiedByNonDirectory(join11(projectDir, slot))) return slot;
5036
- if (rel !== slot) {
5037
- const parts = rel.slice(slot.length + 1).split("/");
5038
- let cursor = slot;
5039
- for (const part of parts.slice(0, -1)) {
5040
- cursor = `${cursor}/${part}`;
5041
- if (occupiedByNonDirectory(join11(projectDir, cursor))) return cursor;
5042
- }
5043
- const leaf = lstatSync(join11(projectDir, rel), { throwIfNoEntry: false });
5044
- if (leaf !== void 0 && !leaf.isFile()) return rel;
5045
- }
5046
- return null;
5047
- }
5048
-
5049
- // src/owned-write.ts
5050
5070
  function createOwnedWriter(projectDir, baseline, options = {}) {
5051
5071
  const { now = /* @__PURE__ */ new Date(), refreshOnly = false, journal, owned = () => false } = options;
5052
5072
  const written = /* @__PURE__ */ new Map();
5053
5073
  const backedUp = [];
5074
+ const judged = [];
5054
5075
  const backupPaths = [];
5055
5076
  const foreignOwned = [];
5056
5077
  const restored = [];
@@ -5075,14 +5096,30 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
5075
5096
  const disk = existsSync9(absPath) ? readFileSync6(absPath, "utf8") : null;
5076
5097
  if (disk === null && refreshOnly && !opts?.createInRefresh && rec.state !== "sha")
5077
5098
  return false;
5078
- const { verdict, record } = judge({ op: "write", kind: "harness", rec, disk, next: content });
5099
+ const { verdict, record, line } = judge({
5100
+ op: "write",
5101
+ kind: "harness",
5102
+ rec,
5103
+ disk,
5104
+ next: content
5105
+ });
5079
5106
  let backupRel = null;
5080
5107
  if (verdict === "backup+overwrite" || verdict === "overwrite" && opts?.backupFirst === true) {
5081
5108
  const backup = backupFile(absPath, now);
5082
5109
  backupPaths.push(backup);
5083
5110
  journal?.backup(backup);
5084
5111
  backupRel = relative2(projectDir, backup).split(sep3).join("/");
5085
- if (verdict === "backup+overwrite" && rec.state !== "none") backedUp.push(rel);
5112
+ if (verdict === "backup+overwrite") {
5113
+ if (rec.state !== "none") backedUp.push(rel);
5114
+ judged.push({
5115
+ path: rel,
5116
+ verdict,
5117
+ recorded: rec.state,
5118
+ line: withBackup(line, backupRel),
5119
+ backup: backupRel,
5120
+ backupAbs: backup
5121
+ });
5122
+ }
5086
5123
  }
5087
5124
  if (record === "displaced" || record === "displaced+sha") {
5088
5125
  displace({ path: rel, change: "displaced", notes: backupRel === null ? [] : [backupRel] });
@@ -5108,6 +5145,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
5108
5145
  outside: outside.list(),
5109
5146
  files: [...written].map(([path, sha256]) => ({ path, sha256 })),
5110
5147
  backedUp: [...backedUp],
5148
+ judged: [...judged],
5111
5149
  backupPaths: [...backupPaths],
5112
5150
  updated,
5113
5151
  restored: [...restored],
@@ -6237,6 +6275,7 @@ function runCliTransforms(params) {
6237
6275
  const externalFiles = [];
6238
6276
  const externalBackups = [];
6239
6277
  const externalBackedUp = [];
6278
+ const externalJudged = [];
6240
6279
  const externalForeignOwned = [];
6241
6280
  const displaced = /* @__PURE__ */ new Map();
6242
6281
  let externalOutside = [];
@@ -6258,6 +6297,7 @@ function runCliTransforms(params) {
6258
6297
  }
6259
6298
  externalBackups.push(...report.ownership.backupPaths);
6260
6299
  externalBackedUp.push(...report.ownership.backedUp);
6300
+ externalJudged.push(...report.ownership.judged ?? []);
6261
6301
  for (const f of report.ownership.displaced ?? [])
6262
6302
  if (!displaced.has(f.path)) displaced.set(f.path, f);
6263
6303
  for (const f of report.ownership.foreignOwned) {
@@ -6365,6 +6405,7 @@ function runCliTransforms(params) {
6365
6405
  externalBackups,
6366
6406
  externalUpdated,
6367
6407
  externalBackedUp,
6408
+ externalJudged,
6368
6409
  displaced: [...displaced.values()],
6369
6410
  externalForeignOwned,
6370
6411
  externalOutside,
@@ -6381,7 +6422,7 @@ function runCliTransforms(params) {
6381
6422
 
6382
6423
  // src/displaced.ts
6383
6424
  init_esm_shims();
6384
- import { lstatSync as lstatSync3, mkdirSync as mkdirSync5, renameSync } from "fs";
6425
+ import { lstatSync as lstatSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync17, renameSync } from "fs";
6385
6426
  import { dirname as dirname5, join as join23 } from "path";
6386
6427
  function exists(abs) {
6387
6428
  try {
@@ -6391,13 +6432,23 @@ function exists(abs) {
6391
6432
  return false;
6392
6433
  }
6393
6434
  }
6435
+ function isLegacyDisplaced(log, projectDir, f) {
6436
+ if (f.change !== "displaced") return false;
6437
+ let original;
6438
+ try {
6439
+ original = readFileSync17(join23(projectDir, f.notes[0] ?? f.path), "utf8");
6440
+ } catch {
6441
+ return false;
6442
+ }
6443
+ return isLegacyHarnessFile(f.path, hashContent(original)) && !isRecordedTrackTarget(log, bundlePath(f.path));
6444
+ }
6394
6445
  function restoreDisplaced(log, projectDir, opts) {
6395
6446
  const out = [];
6396
6447
  for (const f of log.rootFiles ?? []) {
6397
6448
  if (f.change !== "displaced" || !opts.has(f.path)) continue;
6398
6449
  const slot = join23(projectDir, f.path);
6399
6450
  if (outsideProjectTarget(projectDir, slot) !== null) continue;
6400
- const backup = f.notes[0] ?? null;
6451
+ const backup = isLegacyDisplaced(log, projectDir, f) ? null : f.notes[0] ?? null;
6401
6452
  const d2 = judgeDisplaced({
6402
6453
  backup,
6403
6454
  backupExists: backup !== null && exists(join23(projectDir, backup)),
@@ -6466,7 +6517,7 @@ import {
6466
6517
  constants as fsConstants,
6467
6518
  mkdirSync as mkdirSync6,
6468
6519
  readdirSync as readdirSync3,
6469
- readFileSync as readFileSync17,
6520
+ readFileSync as readFileSync18,
6470
6521
  renameSync as renameSync2,
6471
6522
  rmdirSync,
6472
6523
  rmSync as rmSync3
@@ -6484,7 +6535,7 @@ function snapshotSkillRoots(projectDir) {
6484
6535
  for (const rel of listFilesRecursive(join25(projectDir, root))) {
6485
6536
  const path = `${root}/${rel}`;
6486
6537
  try {
6487
- out.set(path, hashContent(readFileSync17(join25(projectDir, path), "utf8")));
6538
+ out.set(path, hashContent(readFileSync18(join25(projectDir, path), "utf8")));
6488
6539
  } catch {
6489
6540
  }
6490
6541
  }
@@ -6677,14 +6728,15 @@ function refreshExternalSkills(projectDir, deps = {}) {
6677
6728
  }
6678
6729
  throw e2;
6679
6730
  }
6680
- const { report, displaced } = settle(ran);
6731
+ const { report, displaced, judged } = settle(ran);
6681
6732
  recordRefreshedFiles(projectDir, log, report, displaced, deps.writeLog ?? writeInstallLog);
6682
6733
  return {
6683
6734
  attempted: report.attempted.length,
6684
6735
  refreshed: report.succeeded,
6685
6736
  failed: report.attempted.filter((r) => !r.ok).map((r) => ({ id: r.asset.id, message: r.message ?? "failed" })),
6686
6737
  notInCatalog,
6687
- unknown: false
6738
+ unknown: false,
6739
+ judged
6688
6740
  };
6689
6741
  }
6690
6742
  function holdSkillSlots(projectDir, hold) {
@@ -6717,7 +6769,11 @@ function holdSkillSlots(projectDir, hold) {
6717
6769
  hold.clis,
6718
6770
  { log: hold.log, scope: hold.scope }
6719
6771
  );
6720
- const out = { backups: [], displaced: [] };
6772
+ const out = {
6773
+ backups: [],
6774
+ displaced: [],
6775
+ judged: []
6776
+ };
6721
6777
  const back = /* @__PURE__ */ new Set();
6722
6778
  const failed = [];
6723
6779
  for (const f of moved) {
@@ -6730,6 +6786,16 @@ function holdSkillSlots(projectDir, hold) {
6730
6786
  }
6731
6787
  out.backups.push(r.backup);
6732
6788
  if (r.displaced !== null) out.displaced.push(r.displaced);
6789
+ const backup = toProjectRel(projectDir, r.backup);
6790
+ const line = !f.recorded ? LINES.hadFile : r.gone ? LINES.editedRemoved : LINES.edited;
6791
+ out.judged.push({
6792
+ path: f.rel,
6793
+ verdict: r.gone ? "backup+remove" : "backup+overwrite",
6794
+ recorded: f.recorded ? "sha" : "none",
6795
+ line: withBackup(line, backup),
6796
+ backup,
6797
+ backupAbs: r.backup
6798
+ });
6733
6799
  } catch (e2) {
6734
6800
  failed.push(`${f.rel} (${e2 instanceof Error ? e2.message : String(e2)})`);
6735
6801
  }
@@ -6781,7 +6847,7 @@ function slotFilesToKeep(projectDir, hold) {
6781
6847
  continue;
6782
6848
  const abs = join25(projectDir, path);
6783
6849
  try {
6784
- if (recordedSha !== void 0 && hashContent(readFileSync17(abs, "utf8")) === recordedSha)
6850
+ if (recordedSha !== void 0 && hashContent(readFileSync18(abs, "utf8")) === recordedSha)
6785
6851
  continue;
6786
6852
  accessSync(abs, fsConstants.R_OK);
6787
6853
  } catch {
@@ -6795,21 +6861,22 @@ function slotFilesToKeep(projectDir, hold) {
6795
6861
  function settleAside(projectDir, f, restore) {
6796
6862
  let now = null;
6797
6863
  try {
6798
- now = readFileSync17(f.abs);
6864
+ now = readFileSync18(f.abs);
6799
6865
  } catch (e2) {
6800
6866
  const code = e2.code;
6801
6867
  if (code !== "ENOENT" && code !== "ENOTDIR") throw e2;
6802
6868
  }
6803
- if (restore || (now === null ? !f.recorded : now.equals(readFileSync17(f.aside)))) {
6869
+ if (restore || (now === null ? !f.recorded : now.equals(readFileSync18(f.aside)))) {
6804
6870
  mkdirSync6(dirname6(f.abs), { recursive: true });
6805
6871
  renameSync2(f.aside, f.abs);
6806
6872
  return "back";
6807
6873
  }
6808
6874
  const backup = backupFromCopy(f.abs, f.aside);
6809
6875
  rmSync3(f.aside, { force: true });
6810
- if (f.recorded || now === null) return { backup, displaced: null };
6876
+ if (f.recorded || now === null) return { backup, gone: now === null, displaced: null };
6811
6877
  return {
6812
6878
  backup,
6879
+ gone: false,
6813
6880
  displaced: { path: f.rel, change: "displaced", notes: [toProjectRel(projectDir, backup)] }
6814
6881
  };
6815
6882
  }
@@ -6935,7 +7002,7 @@ function detectVersion(method, spawn, scope, projectDir) {
6935
7002
  case "npm": {
6936
7003
  const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join25(projectDir, "node_modules", method.pkg, "package.json");
6937
7004
  if (!pkgJson || !existsSync19(pkgJson)) return void 0;
6938
- const parsed = JSON.parse(readFileSync17(pkgJson, "utf8"));
7005
+ const parsed = JSON.parse(readFileSync18(pkgJson, "utf8"));
6939
7006
  return parsed.version;
6940
7007
  }
6941
7008
  default:
@@ -6949,7 +7016,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
6949
7016
  const claudeScope = scope === "global" ? "user" : "project";
6950
7017
  const path = join25(homedir2(), ".claude/plugins/installed_plugins.json");
6951
7018
  if (!existsSync19(path)) return void 0;
6952
- const data = JSON.parse(readFileSync17(path, "utf8"));
7019
+ const data = JSON.parse(readFileSync18(path, "utf8"));
6953
7020
  const entries = data.plugins?.[pluginId] ?? [];
6954
7021
  const matches = entries.filter(
6955
7022
  (e2) => claudeScope === "user" ? e2.scope === "user" : e2.scope === "project" && e2.projectPath === projectDir
@@ -6981,7 +7048,7 @@ function getNpmGlobalRoot(spawn) {
6981
7048
 
6982
7049
  // src/key-ids.ts
6983
7050
  init_esm_shims();
6984
- import { readFileSync as readFileSync18 } from "fs";
7051
+ import { readFileSync as readFileSync19 } from "fs";
6985
7052
  import { join as join26 } from "path";
6986
7053
  function renderedKeyIds(harnessRoot, input, clis) {
6987
7054
  const out = /* @__PURE__ */ new Set();
@@ -7005,7 +7072,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
7005
7072
  for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
7006
7073
  for (const key of gitignoreRender().keys()) add(".gitignore", key);
7007
7074
  if (clis.includes("claude")) {
7008
- const template = readFileSync18(join26(harnessRoot, "templates/settings.json"), "utf8");
7075
+ const template = readFileSync19(join26(harnessRoot, "templates/settings.json"), "utf8");
7009
7076
  for (const key of renderSettingsPortion(template, () => true).keys()) {
7010
7077
  add(".claude/settings.json", key);
7011
7078
  }
@@ -7061,6 +7128,8 @@ function refreshLinkedSkillBodies(params) {
7061
7128
  notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
7062
7129
  files: result.files,
7063
7130
  backupPaths: result.backupPaths,
7131
+ backedUp: result.backedUp,
7132
+ judged: result.judged ?? [],
7064
7133
  displaced: result.displaced ?? [],
7065
7134
  foreignOwned: result.foreignOwned,
7066
7135
  outside: result.outside ?? []
@@ -7069,7 +7138,7 @@ function refreshLinkedSkillBodies(params) {
7069
7138
 
7070
7139
  // src/out-of-track.ts
7071
7140
  init_esm_shims();
7072
- import { lstatSync as lstatSync4, readFileSync as readFileSync19, rmSync as rmSync4 } from "fs";
7141
+ import { lstatSync as lstatSync4, readFileSync as readFileSync20, rmSync as rmSync4 } from "fs";
7073
7142
  import { join as join28 } from "path";
7074
7143
  var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
7075
7144
  var ANCHOR_RULE = "uzys-harness";
@@ -7099,7 +7168,7 @@ function reclaimOutOfTrack(args) {
7099
7168
  out.bringBack[f.path] = tracksBringing(name);
7100
7169
  continue;
7101
7170
  }
7102
- const disk = stat === void 0 ? null : readFileSync19(abs, "utf8");
7171
+ const disk = stat === void 0 ? null : readFileSync20(abs, "utf8");
7103
7172
  const verdict = judge({
7104
7173
  op: "remove",
7105
7174
  kind: "harness",
@@ -7131,7 +7200,7 @@ function tracksBringing(rule) {
7131
7200
 
7132
7201
  // src/state.ts
7133
7202
  init_esm_shims();
7134
- import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
7203
+ import { existsSync as existsSync20, readFileSync as readFileSync21 } from "fs";
7135
7204
  import { join as join29 } from "path";
7136
7205
  var META_FILE = ".claude/.installed-tracks";
7137
7206
  var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
@@ -7160,7 +7229,7 @@ function tracksFromLog(tracks) {
7160
7229
  }
7161
7230
  function collectTraces(projectDir) {
7162
7231
  const at5 = (rel) => join29(projectDir, rel);
7163
- const contains = (rel, needle) => existsSync20(at5(rel)) && readFileSync20(at5(rel), "utf8").includes(needle);
7232
+ const contains = (rel, needle) => existsSync20(at5(rel)) && readFileSync21(at5(rel), "utf8").includes(needle);
7164
7233
  const traces = [];
7165
7234
  if (existsSync20(at5(META_FILE)))
7166
7235
  traces.push({ path: META_FILE, tracks: readMetafile(at5(META_FILE)) });
@@ -7175,7 +7244,7 @@ function collectTraces(projectDir) {
7175
7244
  }
7176
7245
  function readMetafile(path) {
7177
7246
  const seen = /* @__PURE__ */ new Set();
7178
- for (const word of readFileSync20(path, "utf8").split(/\s+/)) {
7247
+ for (const word of readFileSync21(path, "utf8").split(/\s+/)) {
7179
7248
  if (isTrack(word)) seen.add(word);
7180
7249
  }
7181
7250
  return [...seen].sort();
@@ -7220,13 +7289,13 @@ import {
7220
7289
  copyFileSync,
7221
7290
  existsSync as existsSync21,
7222
7291
  readdirSync as readdirSync4,
7223
- readFileSync as readFileSync21,
7292
+ readFileSync as readFileSync22,
7224
7293
  rmdirSync as rmdirSync2,
7225
7294
  statSync as statSync4,
7226
7295
  unlinkSync as unlinkSync2,
7227
7296
  writeFileSync as writeFileSync7
7228
7297
  } from "fs";
7229
- import { basename as basename3, dirname as dirname7, join as join30 } from "path";
7298
+ import { basename as basename3, dirname as dirname7, join as join30, relative as relative5, sep as sep6 } from "path";
7230
7299
  var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
7231
7300
  function buildUpdateSpec(projectDir, tracks, only) {
7232
7301
  const log = readInstallLog(projectDir);
@@ -7288,11 +7357,12 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
7288
7357
  const logAtStart = readInstallLog(projectDir);
7289
7358
  const claudeManaged = logAtStart === null || installedClis(logAtStart).includes("claude");
7290
7359
  report.claudeUnrecorded = !claudeManaged && logAtStart !== null && existsSync21(claudeDir) ? recordClaudeCommand(logAtStart, installedTracks(projectDir)) : null;
7291
- const harness = logAtStart === null ? { displaced: [], restored: [], foreignOwned: [], outside: [] } : updateHarnessFiles({ projectDir, templatesDir, log: logAtStart, wants, outside, report });
7360
+ const harness = logAtStart === null ? { displaced: [], restored: [], foreignOwned: [], outside: [], judged: [] } : updateHarnessFiles({ projectDir, templatesDir, log: logAtStart, wants, outside, report });
7292
7361
  const claudeDisplaced = harness.displaced;
7362
+ const judged = [...harness.judged];
7293
7363
  const prunedRestored = harness.restored;
7294
7364
  if (existsSync21(claudeDir) && claudeManaged) {
7295
- if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report, outside);
7365
+ if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report, outside, judged);
7296
7366
  }
7297
7367
  const harnessMcp = renderHarnessMcp(harnessRoot, {
7298
7368
  spec: { tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS },
@@ -7326,9 +7396,16 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
7326
7396
  if (wants("hooks")) report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
7327
7397
  report.retiredAgents = staleAgentFiles(claudeDir);
7328
7398
  report.demotedAgents = demotedAgentFiles(claudeDir, installedTracks(projectDir));
7329
- const external = wants("external") ? refreshExternalCli(projectDir, harnessRoot, legacyDroppedKeys(logAtStart), harnessMcp) : {
7399
+ const external = wants("external") ? refreshExternalCli(
7400
+ projectDir,
7401
+ harnessRoot,
7402
+ legacyDroppedKeys(logAtStart),
7403
+ harnessMcp,
7404
+ report.skillsSkippedLinks.filter((id) => linksToProjectSharedSkill(projectDir, id))
7405
+ ) : {
7330
7406
  externalUpdated: 0,
7331
7407
  externalBackedUp: [],
7408
+ externalJudged: [],
7332
7409
  displaced: [],
7333
7410
  externalForeignOwned: [],
7334
7411
  externalOutside: [],
@@ -7439,10 +7516,17 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
7439
7516
  const displacedSame = displacedAll.filter((f) => f.notes.length === 0).map((f) => f.path);
7440
7517
  if (displacedMoved.length > 0) report.displaced = displacedMoved;
7441
7518
  if (displacedSame.length > 0) report.displacedSame = displacedSame;
7442
- report.backups = collectRunBackups(projectDir, startedAt, backupsAtStart);
7443
- const onRecord = checksumOnRecord(logAtStart);
7444
- const firstContact = new Set(displacedMoved);
7445
- const noChecksum = report.backups.map((b2) => b2.path).filter((p2) => !onRecord(p2) && !firstContact.has(p2));
7519
+ const deadMcp = claudeManaged ? deadMcpLeft(projectDir) : [];
7520
+ if (deadMcp.length > 0) report.deadMcp = deadMcp;
7521
+ judged.push(...external.externalJudged, ...skillRefresh.judged ?? []);
7522
+ if (judged.length > 0) report.judged = judged;
7523
+ const unmeasured = new Set(
7524
+ judged.filter((j4) => j4.recorded === "no-sha" && j4.backup !== void 0).map((j4) => j4.path)
7525
+ );
7526
+ report.backups = collectRunBackups(projectDir, startedAt, backupsAtStart).map(
7527
+ (b2) => existsSync21(join30(projectDir, b2.path)) ? b2 : { ...b2, removed: true }
7528
+ );
7529
+ const noChecksum = report.backups.map((b2) => b2.path).filter((p2) => unmeasured.has(p2));
7446
7530
  if (noChecksum.length > 0) report.noChecksum = noChecksum;
7447
7531
  writeBackupList(projectDir, report.backups, new Set(noChecksum));
7448
7532
  return report;
@@ -7520,19 +7604,16 @@ function reclaimOutOfTrackRecorded(projectDir, outside) {
7520
7604
  }
7521
7605
  return { reclaim: result, displaced };
7522
7606
  }
7523
- function checksumOnRecord(log) {
7524
- if (log === null) return () => false;
7525
- const paths = /* @__PURE__ */ new Set([
7526
- ...(log.policyFiles ?? []).map((f) => `.claude/${f.path}`),
7527
- ...(log.skillFiles ?? []).map((f) => `.claude/skills/${f.path}`),
7528
- ...(log.externalFiles ?? []).map((f) => f.path),
7529
- ...log.assets.flatMap((a) => a.files ?? []).map((f) => f.path),
7530
- // #800 — 함께 쓰는 파일은 몫마다 sha 가 있다 — 그 백업은 설치자 편집이 확실하다
7531
- ...(log.portions ?? []).map((p2) => p2.path)
7532
- ]);
7533
- const anchor = log.templates.rootClaudeMd;
7534
- if (anchor !== void 0) paths.add(anchor.path);
7535
- return (path) => paths.has(path);
7607
+ function deadMcpLeft(projectDir) {
7608
+ let servers;
7609
+ try {
7610
+ servers = JSON.parse(readFileSync22(join30(projectDir, ".mcp.json"), "utf8")).mcpServers;
7611
+ } catch {
7612
+ return [];
7613
+ }
7614
+ return PRIOR_MCP_SERVERS.flatMap(
7615
+ (p2) => p2.dead !== void 0 && servers?.[p2.name] !== void 0 && jsonSha(servers[p2.name]) === jsonSha(toClaudeServer(p2.value)) ? [{ name: p2.name, note: p2.dead }] : []
7616
+ );
7536
7617
  }
7537
7618
  function retiredFeaturesPart(projectDir, f) {
7538
7619
  if (f.path !== ".codex/config.toml") return {};
@@ -7541,7 +7622,7 @@ function retiredFeaturesPart(projectDir, f) {
7541
7622
  }
7542
7623
  function legacyHookOnDisk(projectDir, rel) {
7543
7624
  try {
7544
- return hasLegacyHarnessHook(readFileSync21(join30(projectDir, rel), "utf8"));
7625
+ return hasLegacyHarnessHook(readFileSync22(join30(projectDir, rel), "utf8"));
7545
7626
  } catch {
7546
7627
  return false;
7547
7628
  }
@@ -7573,7 +7654,7 @@ function writeUpdateSharedFiles(args) {
7573
7654
  const manifest = buildManifest(spec);
7574
7655
  writeSettingsShared(
7575
7656
  writer,
7576
- readFileSync21(settingsSource, "utf8"),
7657
+ readFileSync22(settingsSource, "utf8"),
7577
7658
  projectDir,
7578
7659
  log,
7579
7660
  (script) => {
@@ -7726,6 +7807,7 @@ function updateHarnessTargets(projectDir, templatesDir, log, wants, report, fore
7726
7807
  }
7727
7808
  function updateHarnessFiles(args) {
7728
7809
  const { projectDir, templatesDir, log, wants, outside, report } = args;
7810
+ const judged = [];
7729
7811
  const now = /* @__PURE__ */ new Date();
7730
7812
  const claude = installedClis(log).includes("claude");
7731
7813
  const claims = retiredClaims(log, projectDir, log.policyFiles ?? []);
@@ -7746,7 +7828,7 @@ function updateHarnessFiles(args) {
7746
7828
  if (!claude || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
7747
7829
  const label = `.claude/${dir}`;
7748
7830
  report.updated[label] = 0;
7749
- const ctx = { prefix: dir, outside, recordOf };
7831
+ const ctx = { prefix: dir, outside, recordOf, projectDir, judged };
7750
7832
  report.pruned[label] = pruneOrphans(
7751
7833
  join30(projectDir, label),
7752
7834
  join30(templatesDir, dir),
@@ -7763,7 +7845,8 @@ function updateHarnessFiles(args) {
7763
7845
  targets.refreshedSkills,
7764
7846
  outside,
7765
7847
  recordOf,
7766
- now
7848
+ now,
7849
+ judged
7767
7850
  );
7768
7851
  report.skillsPruned = skillPrune.pruned;
7769
7852
  report.skillsBackedUp.push(...skillPrune.backedUp);
@@ -7772,6 +7855,11 @@ function updateHarnessFiles(args) {
7772
7855
  write: true
7773
7856
  });
7774
7857
  const ledger = writer.ledger();
7858
+ judged.unshift(
7859
+ ...ledger.writes.flatMap(
7860
+ ({ path, verdict, recorded: recorded2, line, backup, backupAbs }) => backup === void 0 || backupAbs === void 0 ? [] : [{ path, verdict, recorded: recorded2, line, backup, backupAbs }]
7861
+ )
7862
+ );
7775
7863
  recordHarnessWrites(projectDir, ledger, {
7776
7864
  claims,
7777
7865
  prunedPolicy,
@@ -7802,10 +7890,25 @@ function updateHarnessFiles(args) {
7802
7890
  displaced: ledger.rootFiles.filter((f) => f.change === "displaced"),
7803
7891
  restored,
7804
7892
  foreignOwned,
7805
- outside: ledger.outside
7893
+ outside: ledger.outside,
7894
+ judged
7895
+ };
7896
+ }
7897
+ function removedRow(projectDir, path, why, judged, backupAbs) {
7898
+ const { line, recorded: recorded2 } = judged;
7899
+ if (backupAbs === null) return { path, verdict: "remove", recorded: recorded2, line: `${why} \u2014 ${line}` };
7900
+ const backup = relative5(projectDir, backupAbs).split(sep6).join("/");
7901
+ return {
7902
+ path,
7903
+ verdict: "backup+remove",
7904
+ recorded: recorded2,
7905
+ line: `${why} \u2014 ${withBackup(line, backup)}`,
7906
+ backup,
7907
+ backupAbs
7806
7908
  };
7807
7909
  }
7808
- function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now) {
7910
+ var NOT_RECORDED_REMOVED = "not on the install record \u2014 saved as <file>.backup-<time>, removed from live";
7911
+ function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now, judged) {
7809
7912
  const pruned = [];
7810
7913
  const backedUp = [];
7811
7914
  for (const id of ids) {
@@ -7816,11 +7919,18 @@ function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now)
7816
7919
  if (foreignOwnedTarget(projectDir, `.claude/skills/${id}/${rel}`) !== null) continue;
7817
7920
  const targetFile = join30(targetSkill, rel);
7818
7921
  if (outside.skip(targetFile)) continue;
7819
- const rec = recordOf(`.claude/skills/${id}/${rel}`);
7820
- if (rec.state !== "sha" || hashContent(readFileSync21(targetFile, "utf8")) !== rec.sha256) {
7821
- backupFile(targetFile, now);
7922
+ const path = `.claude/skills/${id}/${rel}`;
7923
+ const rec = recordOf(path);
7924
+ const disk = rec.state === "sha" ? readFileSync22(targetFile, "utf8") : "";
7925
+ const line = rec.state === "none" ? NOT_RECORDED_REMOVED : judge({ op: "remove", kind: "harness", rec, disk, next: null }).line;
7926
+ let backupAbs = null;
7927
+ if (rec.state !== "sha" || hashContent(disk) !== rec.sha256) {
7928
+ backupAbs = backupFile(targetFile, now);
7822
7929
  backedUp.push(`${id}/${rel}`);
7823
7930
  }
7931
+ judged.push(
7932
+ removedRow(projectDir, path, "not in bundle", { line, recorded: rec.state }, backupAbs)
7933
+ );
7824
7934
  unlinkSync2(targetFile);
7825
7935
  pruned.push(`${id}/${rel}`);
7826
7936
  for (let d2 = dirname7(targetFile); d2 !== targetSkill && readdirSync4(d2).length === 0; d2 = dirname7(d2))
@@ -7895,7 +8005,7 @@ function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new S
7895
8005
  `${JSON.stringify(
7896
8006
  {
7897
8007
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7898
- note: "update \uAC00 \uB0A8\uAE34 \uBC31\uC5C5 \uC30D. \uC6D0\uBCF8(path)\uC740 \uCD5C\uC2E0 \uBC88\uB4E4\uD310, \uBC31\uC5C5(backup)\uC740 \uBC14\uAFB8\uAE30 \uC804 \uD310. noChecksum \uC774 \uC5C6\uB294 \uC30D\uC740 \uAE30\uB85D\uACFC \uB2EC\uB77C\uC9C4 \uD30C\uC77C = \uB2F9\uC2E0\uC758 \uD3B8\uC9D1\uBD84\uC774\uB2E4 \u2014 \uC0C8 \uD310\uC5D0 \uB2E4\uC2DC \uC5B9\uC73C\uB824\uBA74 \uB450 \uD30C\uC77C\uC758 diff \uB97C \uBCF4\uB77C. noChecksum: true \uC778 \uC30D\uC740 \uC61B \uD310\uC774 \uCCB4\uD06C\uC12C\uC744 \uAE30\uB85D\uD558\uC9C0 \uC54A\uC544 \uBE44\uAD50 \uC5C6\uC774 \uD55C \uBC88 \uBC31\uC5C5\uD55C \uAC83 \u2014 \uB300\uAC1C \uB9B4\uB9AC\uC988 \uC0AC\uC774 \uD15C\uD50C\uB9BF \uBCC0\uACBD\uBD84\uC774\uB77C \uB2F9\uC2E0\uC758 \uD3B8\uC9D1\uC774 \uC544\uB2D0 \uC218 \uC788\uB2E4.",
8008
+ note: "update \uAC00 \uB0A8\uAE34 \uBC31\uC5C5 \uC30D. \uC6D0\uBCF8(path)\uC740 \uCD5C\uC2E0 \uBC88\uB4E4\uD310, \uBC31\uC5C5(backup)\uC740 \uBC14\uAFB8\uAE30 \uC804 \uD310. noChecksum \uC774 \uC5C6\uB294 \uC30D\uC740 \uAE30\uB85D\uACFC \uB2EC\uB77C\uC9C4 \uD30C\uC77C = \uB2F9\uC2E0\uC758 \uD3B8\uC9D1\uBD84\uC774\uB2E4 \u2014 \uC0C8 \uD310\uC5D0 \uB2E4\uC2DC \uC5B9\uC73C\uB824\uBA74 \uB450 \uD30C\uC77C\uC758 diff \uB97C \uBCF4\uB77C. noChecksum: true \uC778 \uC30D\uC740 \uC61B \uD310\uC774 \uCCB4\uD06C\uC12C\uC744 \uAE30\uB85D\uD558\uC9C0 \uC54A\uC544 \uBE44\uAD50 \uC5C6\uC774 \uD55C \uBC88 \uBC31\uC5C5\uD55C \uAC83 \u2014 \uB300\uAC1C \uB9B4\uB9AC\uC988 \uC0AC\uC774 \uD15C\uD50C\uB9BF \uBCC0\uACBD\uBD84\uC774\uB77C \uB2F9\uC2E0\uC758 \uD3B8\uC9D1\uC774 \uC544\uB2D0 \uC218 \uC788\uB2E4. removed: true \uC778 \uC30D\uC740 \uADF8 \uD30C\uC77C\uC744 \uC9C0\uC6B0\uAE30 \uC804\uC5D0 \uB0A8\uAE34 \uAC83(\uC740\uD1F4\uD55C \uD558\uB124\uC2A4 \uD30C\uC77C \xB7 \uBC88\uB4E4\uC5D0 \uC5C6\uB294 \uC2A4\uD0AC \uD30C\uC77C \uB4F1) \u2014 \uC6D0\uBCF8 \uC790\uB9AC\uC5D0 \uC0C8 \uD310\uC774 \uC5C6\uB2E4. \uB2F9\uC2E0 \uD30C\uC77C\uC774\uC5C8\uB2E4\uBA74 \uBC31\uC5C5\uC744 \uC6D0\uB798 \uC774\uB984\uC73C\uB85C \uB418\uB3CC\uB9AC\uBA74 \uB41C\uB2E4.",
7899
8009
  backups: backups.map((b2) => noChecksum.has(b2.path) ? { ...b2, noChecksum: true } : b2)
7900
8010
  },
7901
8011
  null,
@@ -7914,7 +8024,7 @@ function trackOnlyFileAssets(tracks) {
7914
8024
  (e2) => e2.type === "file" && e2.applies(optedOut) && e2.applies(optedIn)
7915
8025
  );
7916
8026
  }
7917
- function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @__PURE__ */ new Date()) {
8027
+ function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, now = /* @__PURE__ */ new Date()) {
7918
8028
  if (existsSync21(join30(projectDir, LEGACY_ANCHOR_FILE2))) {
7919
8029
  report.legacyAnchor = LEGACY_ANCHOR_FILE2;
7920
8030
  }
@@ -7924,13 +8034,25 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
7924
8034
  if (outside.skip(anchor)) return;
7925
8035
  const existed = existsSync21(anchor);
7926
8036
  if (existed) {
7927
- const current = readFileSync21(anchor, "utf8");
7928
- if (current !== readFileSync21(templateMd, "utf8")) {
8037
+ const current = readFileSync22(anchor, "utf8");
8038
+ if (current !== readFileSync22(templateMd, "utf8")) {
7929
8039
  const recorded2 = readInstallLog(projectDir)?.templates.rootClaudeMd;
7930
8040
  const owned = recorded2?.path === HARNESS_ANCHOR_FILE && hashContent(current) === recorded2.sha256;
7931
8041
  if (!owned) {
7932
- backupFile(anchor, now);
8042
+ const backup = backupFile(anchor, now);
7933
8043
  report.anchorBackedUp = true;
8044
+ const rel = relative5(projectDir, backup).split(sep6).join("/");
8045
+ const rec = recorded2?.path === HARNESS_ANCHOR_FILE ? { state: "sha", sha256: recorded2.sha256 } : { state: "no-sha" };
8046
+ const next = readFileSync22(templateMd, "utf8");
8047
+ const { line } = judge({ op: "write", kind: "harness", rec, disk: current, next });
8048
+ judged.push({
8049
+ path: HARNESS_ANCHOR_FILE,
8050
+ verdict: "backup+overwrite",
8051
+ recorded: rec.state,
8052
+ line: withBackup(line, rel),
8053
+ backup: rel,
8054
+ backupAbs: backup
8055
+ });
7934
8056
  }
7935
8057
  copyFileSync(templateMd, anchor);
7936
8058
  report.claudeMdUpdated = true;
@@ -7948,7 +8070,7 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
7948
8070
  function upsertRootImport(projectDir, outside) {
7949
8071
  const target = join30(projectDir, "CLAUDE.md");
7950
8072
  if (outside.skip(target)) return false;
7951
- const existing = existsSync21(target) ? readFileSync21(target, "utf8") : null;
8073
+ const existing = existsSync21(target) ? readFileSync22(target, "utf8") : null;
7952
8074
  const next = upsertHarnessImport(existing, {
7953
8075
  projectName: basename3(projectDir),
7954
8076
  tracks: installedTracks(projectDir),
@@ -7976,7 +8098,7 @@ function recordAnchorBaseline(projectDir, anchor) {
7976
8098
  ...log.templates,
7977
8099
  rootClaudeMd: {
7978
8100
  path: HARNESS_ANCHOR_FILE,
7979
- sha256: hashContent(readFileSync21(anchor, "utf8"))
8101
+ sha256: hashContent(readFileSync22(anchor, "utf8"))
7980
8102
  }
7981
8103
  }
7982
8104
  };
@@ -8094,7 +8216,7 @@ function changedRemoteMcp(writes) {
8094
8216
  const changed = /* @__PURE__ */ new Set([...mcp.added, ...mcp.replaced ?? []]);
8095
8217
  return (mcp.remote ?? []).filter((r) => changed.has(r.name));
8096
8218
  }
8097
- function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
8219
+ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp, linkedIds) {
8098
8220
  const log = readInstallLog(projectDir);
8099
8221
  const presentBefore = sharedSkillIdsOnDisk(projectDir);
8100
8222
  const rulesBefore = /* @__PURE__ */ new Set();
@@ -8126,11 +8248,23 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
8126
8248
  // 앞 단계(하네스 파일 쓰기)가 헬퍼만 든 `externalFiles` 를 적어도 같은 답이다 — 헬퍼는 외부 자리로 세지 않는다(B-2)
8127
8249
  owned: recordedOwner(log)
8128
8250
  });
8251
+ const linked = refreshLinkedSkillBodies({
8252
+ harnessRoot,
8253
+ projectDir,
8254
+ ids: linkedIds.filter(
8255
+ (id) => !result.externalFiles.some((f) => f.path === `${SHARED_SKILLS_DIR}/${id}/SKILL.md`)
8256
+ ),
8257
+ baseline: new Map(
8258
+ mergeExternalFiles(log?.externalFiles, result.externalFiles).map((f) => [f.path, f.sha256])
8259
+ )
8260
+ });
8261
+ const written = [...result.externalFiles, ...linked.files];
8262
+ const displaced = [...result.displaced ?? [], ...linked.displaced];
8129
8263
  const anchorPath = result.anchorRetired?.action === "removed" ? result.anchorRetired.path : null;
8130
8264
  const anchorDisplaced = log === null || anchorPath === null ? [] : restoreDisplaced(log, projectDir, { has: (p2) => p2 === anchorPath, write: true });
8131
8265
  if (log) {
8132
8266
  const retiredPath = result.anchorRetired?.forget === true ? result.anchorRetired.path : null;
8133
- const merged = mergeExternalFiles(log.externalFiles, result.externalFiles).filter(
8267
+ const merged = mergeExternalFiles(log.externalFiles, written).filter(
8134
8268
  (f) => f.path !== retiredPath
8135
8269
  );
8136
8270
  const next = retiredPath === null ? { ...log } : { ...log, spec: { ...log.spec, clis: [...installedClis(log)] } };
@@ -8144,7 +8278,7 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
8144
8278
  if (portions.length > 0) next.portions = portions;
8145
8279
  else delete next.portions;
8146
8280
  const created = result.sharedFiles.filter((f) => f.createdAsRoot === true).map((f) => ({ path: f.path, change: "created", notes: ["\uD558\uB124\uC2A4 \uBAAB\uB9CC(\uBE14\uB85D)"] }));
8147
- const roots = [...created, ...withoutLostBackups(log.rootFiles, result.displaced ?? [])];
8281
+ const roots = [...created, ...withoutLostBackups(log.rootFiles, displaced)];
8148
8282
  if (roots.length > 0) next.rootFiles = mergeRootFiles(log.rootFiles, roots);
8149
8283
  if (anchorDisplaced.some((d2) => d2.forget))
8150
8284
  next.rootFiles = withoutRestored(next.rootFiles, anchorDisplaced);
@@ -8203,11 +8337,12 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
8203
8337
  const dropped = new Set(droppedKeys);
8204
8338
  return {
8205
8339
  externalUpdated: result.externalUpdated,
8206
- externalBackedUp: result.externalBackedUp,
8207
- displaced: result.displaced ?? [],
8208
- externalForeignOwned: result.externalForeignOwned,
8209
- externalOutside: result.externalOutside,
8210
- written: result.externalFiles.map((f) => f.path),
8340
+ externalBackedUp: [...result.externalBackedUp, ...linked.backedUp],
8341
+ externalJudged: [...result.externalJudged, ...linked.judged],
8342
+ displaced,
8343
+ externalForeignOwned: [...result.externalForeignOwned, ...linked.foreignOwned],
8344
+ externalOutside: [...result.externalOutside, ...linked.outside],
8345
+ written: written.map((f) => f.path),
8211
8346
  skillsInstalled,
8212
8347
  skillsRestored,
8213
8348
  rulesRestored,
@@ -8262,14 +8397,24 @@ function pruneOrphans(target, source, ext, ctx, now) {
8262
8397
  for (const file of readdirSync4(target)) {
8263
8398
  if (!file.endsWith(ext) || existsSync21(join30(source, file))) continue;
8264
8399
  const targetFile = join30(target, file);
8265
- const rec = ctx.recordOf(`.claude/${ctx.prefix}/${file}`);
8400
+ const path = `.claude/${ctx.prefix}/${file}`;
8401
+ const rec = ctx.recordOf(path);
8266
8402
  try {
8267
- const disk = readFileSync21(targetFile, "utf8");
8268
- const { verdict } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
8403
+ const disk = readFileSync22(targetFile, "utf8");
8404
+ const { verdict, line } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
8269
8405
  if (verdict === "leave" || ctx.outside.skip(targetFile)) continue;
8270
- if (verdict === "backup+remove") backupFile(targetFile, now);
8406
+ const backupAbs = verdict === "backup+remove" ? backupFile(targetFile, now) : null;
8271
8407
  unlinkSync2(targetFile);
8272
8408
  removed.push(file);
8409
+ ctx.judged.push(
8410
+ removedRow(
8411
+ ctx.projectDir,
8412
+ path,
8413
+ "retired from the harness",
8414
+ { line, recorded: rec.state },
8415
+ backupAbs
8416
+ )
8417
+ );
8273
8418
  } catch {
8274
8419
  }
8275
8420
  }
@@ -8330,10 +8475,10 @@ function runInstall(ctx) {
8330
8475
  const settingsPath = join31(projectDir, ".claude", "settings.json");
8331
8476
  if (existsSync22(settingsPath)) {
8332
8477
  try {
8333
- JSON.parse(readFileSync22(settingsPath, "utf8"));
8478
+ JSON.parse(readFileSync23(settingsPath, "utf8"));
8334
8479
  } catch {
8335
8480
  throw new Error(
8336
- `${relative5(process.cwd(), settingsPath)} is not valid JSON \u2014 left untouched, so no hooks were wired. Fix or delete that file, then run the install again`
8481
+ `${relative6(process.cwd(), settingsPath)} is not valid JSON \u2014 left untouched, so no hooks were wired. Fix or delete that file, then run the install again`
8337
8482
  );
8338
8483
  }
8339
8484
  }
@@ -8402,6 +8547,7 @@ function runInstallStages(ctx, args) {
8402
8547
  externalBackups,
8403
8548
  externalUpdated: _externalUpdated,
8404
8549
  externalBackedUp: _externalBackedUp,
8550
+ externalJudged: _externalJudged,
8405
8551
  displaced: cliDisplaced = [],
8406
8552
  externalForeignOwned,
8407
8553
  externalOutside,
@@ -8649,7 +8795,7 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
8649
8795
  if (path === null || done.has(path) || !existsSync22(join31(projectDir, path))) return [];
8650
8796
  const name = adapterFor(path);
8651
8797
  const keys = [...excludedKeys(path, [id])];
8652
- const present2 = name === null ? null : ADAPTERS[name].read(readFileSync22(join31(projectDir, path), "utf8"), keys, projectDir);
8798
+ const present2 = name === null ? null : ADAPTERS[name].read(readFileSync23(join31(projectDir, path), "utf8"), keys, projectDir);
8653
8799
  return present2 !== null && present2.size === 0 ? [] : [{ id, path }];
8654
8800
  });
8655
8801
  }
@@ -8810,7 +8956,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
8810
8956
  function writeSettingsPortion(writer, source, projectDir, previousLog, hookInstalled) {
8811
8957
  return writeSettingsShared(
8812
8958
  writer,
8813
- readFileSync22(source, "utf8"),
8959
+ readFileSync23(source, "utf8"),
8814
8960
  projectDir,
8815
8961
  previousLog,
8816
8962
  hookInstalled
@@ -8954,7 +9100,7 @@ function recordInterruptedInstall(ctx, error, run) {
8954
9100
  const codexTrust = registeredTrust(journal.codexOptIn, ctx.projectDir);
8955
9101
  const written = writtenSoFar(ledger, cliFiles, rootFiles, stage.rootImportWritten);
8956
9102
  if (codexTrust) written.push(`${codexTrust.configPath} (Codex trust entry for this folder)`);
8957
- const projectRel = (abs) => relative5(ctx.projectDir, abs).split(sep6).join("/");
9103
+ const projectRel = (abs) => relative6(ctx.projectDir, abs).split(sep7).join("/");
8958
9104
  const writtenSet = new Set(written);
8959
9105
  const backups = [...own.backups, ...journal.backups].map((abs) => {
8960
9106
  const original = projectRel(backupOriginal(abs) ?? abs);
@@ -9096,7 +9242,7 @@ function accumulateCategory(cats, entry) {
9096
9242
  function writeRootClaudeMd(projectDir, tracks, continuousSkills, harnessRoot, writer) {
9097
9243
  const target = join31(projectDir, "CLAUDE.md");
9098
9244
  if (writer.skipOutside(target)) return null;
9099
- const existing = existsSync22(target) ? readFileSync22(target, "utf-8") : null;
9245
+ const existing = existsSync22(target) ? readFileSync23(target, "utf-8") : null;
9100
9246
  const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
9101
9247
  const content = upsertHarnessImport(existing, {
9102
9248
  projectName: basename4(projectDir),
@@ -9130,8 +9276,8 @@ function listHookFiles(hookDir) {
9130
9276
  // src/commands/install-render.ts
9131
9277
  init_esm_shims();
9132
9278
  import { homedir as homedir3 } from "os";
9133
- import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync24 } from "fs";
9134
- import { join as join33, relative as relative6 } from "path";
9279
+ import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync25 } from "fs";
9280
+ import { join as join33, relative as relative7 } from "path";
9135
9281
 
9136
9282
  // src/preset-recommend.ts
9137
9283
  init_esm_shims();
@@ -9167,7 +9313,7 @@ function groupAssetsByCategory(assetIds) {
9167
9313
 
9168
9314
  // src/resident-entries.ts
9169
9315
  init_esm_shims();
9170
- import { existsSync as existsSync23, readFileSync as readFileSync23 } from "fs";
9316
+ import { existsSync as existsSync23, readFileSync as readFileSync24 } from "fs";
9171
9317
  import { basename as basename5, join as join32 } from "path";
9172
9318
  function residentEntries(spec) {
9173
9319
  const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync23(join32(spec.projectDir, d2, id)));
@@ -9208,7 +9354,7 @@ function plannedClaudeContext(spec) {
9208
9354
  if (!spec.cli.includes("claude")) return void 0;
9209
9355
  const root = resolveBundleRoot();
9210
9356
  const rootClaude = join32(spec.projectDir, "CLAUDE.md");
9211
- const existing = existsSync23(rootClaude) ? readFileSync23(rootClaude, "utf-8") : null;
9357
+ const existing = existsSync23(rootClaude) ? readFileSync24(rootClaude, "utf-8") : null;
9212
9358
  const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
9213
9359
  if (existing === null && seeded === null) return void 0;
9214
9360
  const manifestSpec = buildManifestSpec(spec);
@@ -9359,9 +9505,11 @@ function renderUpdateSummary(log, spec, report) {
9359
9505
  });
9360
9506
  const backups = report.updateMode?.backups ?? [];
9361
9507
  if (backups.length > 0) {
9362
- const unmeasured = report.updateMode?.noChecksum?.length ?? 0;
9363
- const unmeasuredPart = unmeasured === 0 ? "" : unmeasured === backups.length ? " \u2014 no checksum on record for any of them (installed before checksums were kept), so they may not be your edits" : ` \u2014 ${unmeasured} had no checksum on record (marked noChecksum in the list), so those may not be your edits`;
9364
- const removedBefore = report.updateMode?.outOfTrack?.backedUp.length ?? 0;
9508
+ const replaced = backups.filter((b2) => b2.removed !== true);
9509
+ const removedBefore = backups.length - replaced.length;
9510
+ const noChecksum = new Set(report.updateMode?.noChecksum ?? []);
9511
+ const unmeasured = replaced.filter((b2) => noChecksum.has(b2.path)).length;
9512
+ const unmeasuredPart = unmeasured === 0 ? "" : unmeasured === backups.length ? " \u2014 no checksum on record for any of them (installed before checksums were kept), so they may not be your edits" : ` \u2014 ${unmeasured} ${removedBefore > 0 ? "replaced " : ""}had no checksum on record (marked noChecksum in the list), so those may not be your edits`;
9365
9513
  const how = removedBefore === 0 ? "saved before replacing" : removedBefore === backups.length ? "saved before removing" : `saved before replacing (${removedBefore} before removing)`;
9366
9514
  log(
9367
9515
  infoRow(
@@ -9369,12 +9517,13 @@ function renderUpdateSummary(log, spec, report) {
9369
9517
  `${backups.length} file(s) ${how}, as *.backup-<time>${unmeasuredPart} \xB7 list: .uzys-agent-harness/update-backups.json`
9370
9518
  )
9371
9519
  );
9372
- const displacedN = report.updateMode?.displaced?.length ?? 0;
9373
- if (removedBefore + displacedN < backups.length)
9520
+ const displaced = new Set(report.updateMode?.displaced ?? []);
9521
+ const carry = replaced.filter((b2) => !displaced.has(b2.path)).length;
9522
+ if (carry > 0)
9374
9523
  log(
9375
9524
  infoRow(
9376
9525
  "NEXT",
9377
- unmeasured === backups.length ? 'nothing to re-apply unless you remember editing one of them \u2014 then ask audit-harness-fit: "update \uBC31\uC5C5\uBCF8\uC758 \uB0B4 \uD3B8\uC9D1\uC744 \uC0C8 \uD310\uC5D0 \uB2E4\uC2DC \uC5B9\uC5B4\uC918"' : 'to re-apply your edits on the new version, ask audit-harness-fit: "update \uBC31\uC5C5\uBCF8\uC758 \uB0B4 \uD3B8\uC9D1\uC744 \uC0C8 \uD310\uC5D0 \uB2E4\uC2DC \uC5B9\uC5B4\uC918"'
9526
+ unmeasured === carry ? 'nothing to carry over unless you remember editing one of them \u2014 then ask audit-harness-fit: "update \uBC31\uC5C5\uBCF8\uC758 \uB0B4 \uD3B8\uC9D1\uC744 \uB0B4 \uD30C\uC77C\uB85C \uC62E\uACA8\uC918"' : 'backups are not loaded \u2014 to keep your edits across updates, ask audit-harness-fit: "update \uBC31\uC5C5\uBCF8\uC758 \uB0B4 \uD3B8\uC9D1\uC744 \uB0B4 \uD30C\uC77C\uB85C \uC62E\uACA8\uC918" (update replaces harness files every time, so edits put back inside them return as backups)'
9378
9527
  )
9379
9528
  );
9380
9529
  }
@@ -9671,7 +9820,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
9671
9820
  const home = process.env.CODEX_HOME ?? join33(homedir3(), ".codex");
9672
9821
  const configPath = join33(home, "config.toml");
9673
9822
  if (!existsSync24(configPath)) return false;
9674
- return hasTrustEntry(readFileSync24(configPath, "utf8"), spec.projectDir);
9823
+ return hasTrustEntry(readFileSync25(configPath, "utf8"), spec.projectDir);
9675
9824
  } catch {
9676
9825
  return false;
9677
9826
  }
@@ -9712,7 +9861,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
9712
9861
  }
9713
9862
  function hasFillPrompt(path) {
9714
9863
  try {
9715
- return readFileSync24(path, "utf8").includes("<!-- FILL:");
9864
+ return readFileSync25(path, "utf8").includes("<!-- FILL:");
9716
9865
  } catch {
9717
9866
  return false;
9718
9867
  }
@@ -9766,25 +9915,8 @@ function linkedSkillRows(log, updated, notOurs) {
9766
9915
  );
9767
9916
  }
9768
9917
  }
9769
- function hadFileRow(log, area, paths) {
9770
- if (paths.length === 0) return;
9771
- log(
9772
- assetRow(
9773
- "skip",
9774
- `${area} had files with these names`,
9775
- `${paths.length} backed up as *.backup-<time> \u2014 yours, put back on uninstall \xB7 ${paths.join(", ")}`
9776
- )
9777
- );
9778
- }
9779
9918
  function renderPhase1Rows(log, baseline, verbose = false) {
9780
9919
  if (baseline.updateMode) {
9781
- const noChecksum = new Set(baseline.updateMode.noChecksum ?? []);
9782
- const displaced = baseline.updateMode.displaced ?? [];
9783
- const displacedSkills = displaced.filter((p2) => p2.startsWith(".claude/skills/"));
9784
- const displacedPolicy = displaced.filter(
9785
- (p2) => p2.startsWith(".claude/") && !p2.startsWith(".claude/skills/")
9786
- );
9787
- const displacedExternal = displaced.filter((p2) => !p2.startsWith(".claude/"));
9788
9920
  for (const [dir, count] of Object.entries(baseline.updateMode.updated)) {
9789
9921
  if (count > 0) log(assetRow("success", dir, `${count} files updated`));
9790
9922
  }
@@ -9841,6 +9973,8 @@ function renderPhase1Rows(log, baseline, verbose = false) {
9841
9973
  const row = sharedFileRow(f);
9842
9974
  if (row !== null) log(row);
9843
9975
  }
9976
+ for (const d2 of baseline.updateMode.deadMcp ?? [])
9977
+ log(assetRow("skip", ".mcp.json", `${d2.name} \u2014 ${d2.note}`));
9844
9978
  for (const f of baseline.updateMode.sharedLeft ?? []) {
9845
9979
  log(sharedLeftRow(f));
9846
9980
  if (f.retiredFeatures !== void 0) log(retiredFeaturesRow(f.path, f.retiredFeatures));
@@ -9854,11 +9988,7 @@ function renderPhase1Rows(log, baseline, verbose = false) {
9854
9988
  if (legacy2 !== null) log(legacy2);
9855
9989
  for (const row of legacyReleasedCatalogRows(baseline.updateMode.legacyReleasedCatalog ?? []))
9856
9990
  log(row);
9857
- for (const [dir, removed] of Object.entries(baseline.updateMode.pruned)) {
9858
- if (removed.length > 0) {
9859
- log(assetRow("skip", `${dir} orphan prune`, `${removed.length} removed`));
9860
- }
9861
- }
9991
+ for (const j4 of baseline.updateMode.judged ?? []) log(judgedRow(j4));
9862
9992
  if (baseline.updateMode.skippedGroups.length > 0) {
9863
9993
  log(
9864
9994
  assetRow(
@@ -9871,15 +10001,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
9871
10001
  if (baseline.updateMode.claudeMdUpdated) {
9872
10002
  log(assetRow("success", HARNESS_ANCHOR_FILE, "refreshed from template"));
9873
10003
  }
9874
- if (baseline.updateMode.anchorBackedUp && !noChecksum.has(HARNESS_ANCHOR_FILE)) {
9875
- log(
9876
- assetRow(
9877
- "skip",
9878
- `${HARNESS_ANCHOR_FILE} edited`,
9879
- "your edits backed up as *.backup-<time> \u2014 latest template is now active"
9880
- )
9881
- );
9882
- }
9883
10004
  if (baseline.updateMode.anchorCreated) {
9884
10005
  log(assetRow("success", HARNESS_ANCHOR_FILE, "created from template (anchor migration)"));
9885
10006
  }
@@ -9907,28 +10028,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
9907
10028
  if (baseline.updateMode.legacyAnchor) {
9908
10029
  log(assetRow("skip", baseline.updateMode.legacyAnchor, LEGACY_ANCHOR_NOTE));
9909
10030
  }
9910
- const skillsEdited = baseline.updateMode.skillsBackedUp.filter(
9911
- (p2) => !noChecksum.has(`.claude/skills/${p2}`)
9912
- );
9913
- if (skillsEdited.length > 0) {
9914
- log(
9915
- assetRow(
9916
- "skip",
9917
- ".claude/skills edited files",
9918
- `${skillsEdited.length} backed up as *.backup-<time>`
9919
- )
9920
- );
9921
- }
9922
- hadFileRow(log, ".claude/skills", displacedSkills);
9923
- if (baseline.updateMode.skillsPruned.length > 0) {
9924
- log(
9925
- assetRow(
9926
- "success",
9927
- ".claude/skills not in bundle",
9928
- `${baseline.updateMode.skillsPruned.length} files deleted \xB7 ${baseline.updateMode.skillsPruned.join(", ")}`
9929
- )
9930
- );
9931
- }
9932
10031
  linkedSkillRows(
9933
10032
  log,
9934
10033
  baseline.updateMode.skillsLinked ?? [],
@@ -9982,29 +10081,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
9982
10081
  )
9983
10082
  );
9984
10083
  }
9985
- const policyEdited = baseline.updateMode.policyBackedUp.filter(
9986
- (p2) => !noChecksum.has(`.claude/${p2}`)
9987
- );
9988
- if (policyEdited.length > 0) {
9989
- log(
9990
- assetRow(
9991
- "skip",
9992
- "edited policy files",
9993
- `${policyEdited.length} backed up as *.backup-<time>`
9994
- )
9995
- );
9996
- }
9997
- hadFileRow(log, "policy files", displacedPolicy);
9998
- const unmeasured = [...noChecksum].filter((p2) => p2 !== ".mcp-allowlist");
9999
- if (unmeasured.length > 0) {
10000
- log(
10001
- assetRow(
10002
- "skip",
10003
- "no checksum on record",
10004
- `${unmeasured.length} backed up once as *.backup-<time> \u2014 installed before checksums were kept, so these may not be your edits (usually what changed between releases); later updates compare precisely`
10005
- )
10006
- );
10007
- }
10008
10084
  const retiredRefs = baseline.updateMode.retiredHookRefsRemoved ?? [];
10009
10085
  if (retiredRefs.length > 0) {
10010
10086
  log(
@@ -10054,17 +10130,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
10054
10130
  )
10055
10131
  );
10056
10132
  }
10057
- const externalEdited = baseline.updateMode.externalBackedUp.filter((p2) => !noChecksum.has(p2));
10058
- if (externalEdited.length > 0) {
10059
- log(
10060
- assetRow(
10061
- "skip",
10062
- "edited external CLI files",
10063
- `${externalEdited.length} backed up as *.backup-<time>`
10064
- )
10065
- );
10066
- }
10067
- hadFileRow(log, "external CLI files", displacedExternal);
10068
10133
  const displacedSame = baseline.updateMode.displacedSame ?? [];
10069
10134
  if (displacedSame.length > 0) {
10070
10135
  log(
@@ -10525,7 +10590,7 @@ function renderInterruptedInstall(err, e2, cwd = process.cwd()) {
10525
10590
  }
10526
10591
  const { path, error } = e2.record;
10527
10592
  err(
10528
- path !== null ? ` Already in place before it stopped \u2014 recorded in ${relative6(cwd, path) || path}:` : " Already in place before it stopped:"
10593
+ path !== null ? ` Already in place before it stopped \u2014 recorded in ${relative7(cwd, path) || path}:` : " Already in place before it stopped:"
10529
10594
  );
10530
10595
  for (const [key, n] of groups) {
10531
10596
  err(` ${key.endsWith("/") ? `${key} (${n} file${n === 1 ? "" : "s"})` : key}`);
@@ -10848,7 +10913,7 @@ function registerInstallCommand(cli) {
10848
10913
 
10849
10914
  // src/commands/list.ts
10850
10915
  init_esm_shims();
10851
- import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
10916
+ import { existsSync as existsSync25, readFileSync as readFileSync26 } from "fs";
10852
10917
  import { join as join34, resolve as resolve3 } from "path";
10853
10918
  function listAction(options = {}, deps = {}) {
10854
10919
  const log = deps.log ?? console.log;
@@ -10953,7 +11018,7 @@ function formatHarnessFileRows(log, projectDir) {
10953
11018
  const rootMd = log.templates.rootClaudeMd;
10954
11019
  if (rootMd) {
10955
11020
  const path = join34(projectDir, rootMd.path);
10956
- const modified = existsSync25(path) && hashContent(readFileSync25(path, "utf8")) !== rootMd.sha256;
11021
+ const modified = existsSync25(path) && hashContent(readFileSync26(path, "utf8")) !== rootMd.sha256;
10957
11022
  rows2.push(
10958
11023
  modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
10959
11024
  );
@@ -10990,7 +11055,7 @@ import {
10990
11055
  constants as fsConstants2,
10991
11056
  lstatSync as lstatSync5,
10992
11057
  readdirSync as readdirSync7,
10993
- readFileSync as readFileSync26,
11058
+ readFileSync as readFileSync27,
10994
11059
  readlinkSync,
10995
11060
  realpathSync as realpathSync3,
10996
11061
  rmdirSync as rmdirSync3,
@@ -10998,7 +11063,7 @@ import {
10998
11063
  statSync as statSync5,
10999
11064
  writeFileSync as writeFileSync9
11000
11065
  } from "fs";
11001
- import { dirname as dirname8, join as join35, relative as relative7, resolve as resolve4, sep as sep7 } from "path";
11066
+ import { dirname as dirname8, join as join35, relative as relative8, resolve as resolve4, sep as sep8 } from "path";
11002
11067
 
11003
11068
  // src/uninstall-interactive.ts
11004
11069
  init_esm_shims();
@@ -12087,7 +12152,8 @@ var TRACK_LABELS = {
12087
12152
  "csr-supabase": "csr-supabase \u2014 Vite + React + Supabase",
12088
12153
  "csr-fastify": "csr-fastify \u2014 Vite + React + Fastify",
12089
12154
  "csr-fastapi": "csr-fastapi \u2014 Vite + React + FastAPI",
12090
- "ssr-htmx": "ssr-htmx \u2014 htmx + FastAPI",
12155
+ "ssr-htmx": "ssr-htmx \u2014 htmx + FastAPI (Python) + daisyUI",
12156
+ "ssr-htmx-fastify": "ssr-htmx-fastify \u2014 htmx + Fastify (Node) + daisyUI",
12091
12157
  "ssr-nextjs": "ssr-nextjs \u2014 Next.js (App Router)",
12092
12158
  data: "data \u2014 Python data / DuckDB / PySide6",
12093
12159
  executive: "executive \u2014 proposals / DD / pitch (no agent-skills)",
@@ -12926,12 +12992,7 @@ function settleLog(ctx, io) {
12926
12992
  const recordDir = join35(projectDir, INSTALL_LOG_DIR);
12927
12993
  if (outsideLinkAt(projectDir, INSTALL_LOG_DIR) !== null) io.rm(recordDir);
12928
12994
  else {
12929
- for (const rel of [
12930
- `${INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`,
12931
- ...HARNESS_RUNTIME_IGNORES
12932
- ]) {
12933
- io.rm(join35(projectDir, rel));
12934
- }
12995
+ for (const rel of RECORD_FILES) io.rm(join35(projectDir, rel));
12935
12996
  pruneEmptyDirsUpward(projectDir, recordDir);
12936
12997
  }
12937
12998
  if (outsideLinkAt(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
@@ -13117,13 +13178,16 @@ function resolveCliTarget(ctx, io) {
13117
13178
  function underAny(path, owned) {
13118
13179
  return owned.some((o) => o.endsWith("/") ? path.startsWith(o) : path === o);
13119
13180
  }
13120
- function planCliRemoval(target, remaining) {
13181
+ function planCliRemoval(target, remaining, log, projectDir) {
13121
13182
  const { exclusive, sharedNowUnowned } = removableFor(target, remaining);
13122
13183
  const owned = [...exclusive, ...sharedNowUnowned];
13123
13184
  const prefixes = owned.filter((o) => o.kind === "recorded" || o.kind === "anchor").map((o) => o.path);
13124
- const has = (path) => underAny(path, prefixes);
13185
+ const used = (path) => usersOf(path, projectDir).some((cli) => remaining.includes(cli));
13186
+ const has = (path) => underAny(path, prefixes) && !used(path);
13187
+ const kept = harnessCandidates(log).filter((p2) => underAny(p2, prefixes) && used(p2)).map((p2) => p2.split("/").slice(0, 3).join("/"));
13125
13188
  return {
13126
13189
  target,
13190
+ kept: [...new Set(kept)].sort(),
13127
13191
  scope: {
13128
13192
  has,
13129
13193
  // `.claude/settings.json` 은 claude 의 `.claude/` 아래 · `.codex/config.toml` 은 codex 의 `.codex/` 아래 · `AGENTS.md` ·
@@ -13137,6 +13201,11 @@ function planCliRemoval(target, remaining) {
13137
13201
  }
13138
13202
  };
13139
13203
  }
13204
+ function keptForRemainingLines(plan, preview) {
13205
+ if (plan.kept.length === 0) return [];
13206
+ const what = `${plan.kept.join(" \xB7 ")} \u2014 still used by Claude Code through its .claude/skills/ link(s)`;
13207
+ return [preview ? ` \u25CB leave ${what}` : ` ${c.yellow("\u2298")} left ${what}`];
13208
+ }
13140
13209
  function removeCliHeader(target, installed, remaining) {
13141
13210
  return [
13142
13211
  "",
@@ -13158,6 +13227,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
13158
13227
  harnessRoot
13159
13228
  });
13160
13229
  lines2.push(...harnessRemovalLines(preview, installLog, projectDir, plan.scope, true));
13230
+ lines2.push(...keptForRemainingLines(plan, true));
13161
13231
  lines2.push(...assetsLeftLines(installLog, projectDir, plan.scope));
13162
13232
  const rerender = agentsMdRerenderSet(plan.target, remaining, installLog, projectDir);
13163
13233
  if (rerender !== null) {
@@ -13182,7 +13252,7 @@ function agentsMdRerenderSet(target, remaining, log, projectDir) {
13182
13252
  }
13183
13253
  function readTextOrEmpty(abs) {
13184
13254
  try {
13185
- return readFileSync26(abs, "utf8");
13255
+ return readFileSync27(abs, "utf8");
13186
13256
  } catch {
13187
13257
  return "";
13188
13258
  }
@@ -13267,7 +13337,7 @@ function removeCliAction(ctx, io) {
13267
13337
  const { options, installLog, projectDir, harnessRoot } = ctx;
13268
13338
  const installed = installedClis(installLog);
13269
13339
  const remaining = installed.filter((cli) => cli !== target);
13270
- const plan = planCliRemoval(target, remaining);
13340
+ const plan = planCliRemoval(target, remaining, installLog, projectDir);
13271
13341
  for (const line of removeCliHeader(target, installed, remaining)) io.log(line);
13272
13342
  if (options.dryRun) {
13273
13343
  for (const line of removeCliDryRunLines(plan, ctx, remaining)) io.log(line);
@@ -13282,6 +13352,7 @@ function removeCliAction(ctx, io) {
13282
13352
  for (const line of harnessRemovalLines(done, installLog, projectDir, plan.scope, false)) {
13283
13353
  io.log(line);
13284
13354
  }
13355
+ for (const line of keptForRemainingLines(plan, false)) io.log(line);
13285
13356
  for (const line of assetsLeftLines(installLog, projectDir, plan.scope)) io.log(line);
13286
13357
  const rerenderSet = agentsMdRerenderSet(target, remaining, installLog, projectDir);
13287
13358
  const rerender = rerenderSet === null ? null : rerenderAgentsMd(ctx, rerenderSet);
@@ -13323,7 +13394,7 @@ function remnantFor(path, log, harnessRoot, projectDir, wiringGone = []) {
13323
13394
  const gone = (k2) => wiringGone.includes(`hooks/${k2.split("#")[1] ?? ""}`);
13324
13395
  return (disk) => {
13325
13396
  try {
13326
- const template = readFileSync26(join35(harnessRoot, "templates", "settings.json"), "utf8");
13397
+ const template = readFileSync27(join35(harnessRoot, "templates", "settings.json"), "utf8");
13327
13398
  const render = renderSettingsPortion(template, () => true);
13328
13399
  return [...legacySettingsSeed(disk, render, projectDir, { edited: true }).keys()].filter(
13329
13400
  (k2) => !k2.endsWith("{}") && !gone(k2)
@@ -13547,11 +13618,16 @@ var LEGACY_LOG = `${LEGACY_INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`;
13547
13618
  var SKILLS_DIR3 = ".claude/skills/";
13548
13619
  var CLI_FOLDERS2 = [".claude", ".codex", ".opencode"];
13549
13620
  var LEFT_FILES_SHOWN = 3;
13621
+ var RECORD_FILES = [
13622
+ `${INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`,
13623
+ ...HARNESS_RUNTIME_IGNORES
13624
+ ];
13550
13625
  var FULL_SCOPE = {
13551
13626
  has: () => true,
13552
13627
  shared: UNINSTALL_SHARED,
13553
13628
  skeleton: PROJECT_SKELETON_DIRS,
13554
- folders: CLI_FOLDERS2,
13629
+ // #817 — 기록 폴더도 — 헬퍼는 파일 하나씩 회수하고(#812) 설치자 파일은 남으니, 폴더가 왜 남았는지 같은 줄로 말한다
13630
+ folders: [...CLI_FOLDERS2, INSTALL_LOG_DIR],
13555
13631
  importBlock: true,
13556
13632
  anchor: true
13557
13633
  };
@@ -13646,7 +13722,7 @@ function templateNext(harnessRoot, log, path) {
13646
13722
  if (helper === void 0 && !path.startsWith(CLAUDE_DIR3)) return null;
13647
13723
  try {
13648
13724
  const source = helper ?? path.slice(CLAUDE_DIR3.length);
13649
- return readFileSync26(join35(harnessRoot, "templates", source), "utf8");
13725
+ return readFileSync27(join35(harnessRoot, "templates", source), "utf8");
13650
13726
  } catch {
13651
13727
  return null;
13652
13728
  }
@@ -13690,13 +13766,13 @@ function removeRecordedFile(path, ctx) {
13690
13766
  return;
13691
13767
  }
13692
13768
  if (st2.isSymbolicLink()) effective = real;
13693
- disk = readFileSync26(effective, "utf8");
13769
+ disk = readFileSync27(effective, "utf8");
13694
13770
  } catch {
13695
13771
  out.unjudged.push(path);
13696
13772
  return;
13697
13773
  }
13698
13774
  const rec = recorded(log, path, { isTarget: ctx.isTarget });
13699
- const legacy = rec.state !== "sha" && isLegacyHarnessFile(path, hashContent(disk)) && (rec.state === "no-sha" || mayReclaimLegacy(log, path));
13775
+ const legacy = rec.state !== "sha" && isLegacyHarnessFile(path, hashContent(disk)) && (rec.state === "no-sha" || mayReclaimLegacy(log, projectDir, path));
13700
13776
  const inBundle = legacy && ctx.isTarget(bundlePath(path));
13701
13777
  const verdict = judge({
13702
13778
  op: "remove",
@@ -13708,7 +13784,7 @@ function removeRecordedFile(path, ctx) {
13708
13784
  if (verdict.verdict !== "remove" && verdict.verdict !== "backup+remove") return;
13709
13785
  let backup = null;
13710
13786
  if (verdict.verdict === "backup+remove") {
13711
- backup = io.write ? relative7(projectDir, backupFile(effective)).split(sep7).join("/") : `${relative7(projectDir, effective).split(sep7).join("/")}.backup-<time>`;
13787
+ backup = io.write ? relative8(projectDir, backupFile(effective)).split(sep8).join("/") : `${relative8(projectDir, effective).split(sep8).join("/")}.backup-<time>`;
13712
13788
  }
13713
13789
  if (io.write) {
13714
13790
  if (effective !== abs) io.rm(abs);
@@ -13726,14 +13802,13 @@ function legacyFileCandidates() {
13726
13802
  (p2) => p2.startsWith(SKILLS_DIR3) ? [p2, `.agents/skills/${p2.slice(SKILLS_DIR3.length)}`] : [p2]
13727
13803
  );
13728
13804
  }
13729
- function bundlePath(path) {
13730
- return path.startsWith(".agents/skills/") ? `${SKILLS_DIR3}${path.slice(".agents/skills/".length)}` : path;
13731
- }
13732
- function mayReclaimLegacy(log, path) {
13805
+ function mayReclaimLegacy(log, projectDir, path) {
13733
13806
  const clis = installedClis(log);
13734
- if (!ownersOf(path).some((c3) => clis.includes(c3))) return false;
13807
+ if (!usersOf(path, projectDir).some((c3) => clis.includes(c3))) return false;
13735
13808
  if (isExcludedPath(bundlePath(path), excludedIds(log))) return false;
13736
- return !(log.rootFiles ?? []).some((f) => f.path === path && f.change === "displaced");
13809
+ return !(log.rootFiles ?? []).some(
13810
+ (f) => f.path === path && f.change === "displaced" && !isLegacyDisplaced(log, projectDir, f)
13811
+ );
13737
13812
  }
13738
13813
  function removeLegacyHookWiring(log, projectDir, scope, io, removed) {
13739
13814
  if (!scope.has(SETTINGS_TARGET) || !installedClis(log).includes("claude")) return [];
@@ -13816,7 +13891,7 @@ function removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom) {
13816
13891
  return true;
13817
13892
  }
13818
13893
  if (lstatSync5(abs).isSymbolicLink()) effective = real;
13819
- current = readFileSync26(effective, "utf8");
13894
+ current = readFileSync27(effective, "utf8");
13820
13895
  } catch {
13821
13896
  out.unjudged.push(path);
13822
13897
  return true;
@@ -14012,7 +14087,7 @@ function leftFolderLines(r, log, projectDir, scope, preview) {
14012
14087
  const entries = /* @__PURE__ */ new Map();
14013
14088
  for (const n of yoursUnder(projectDir, dir)) {
14014
14089
  const full = `${dir}/${n.name}`;
14015
- if (full === LEGACY_LOG) continue;
14090
+ if (full === LEGACY_LOG || RECORD_FILES.includes(full)) continue;
14016
14091
  if (!gone.has(full) && !assetFiles.has(full) && !said(full)) entries.set(n.name, n.shown);
14017
14092
  }
14018
14093
  for (const p2 of back)
@@ -14079,7 +14154,7 @@ function removeAnchor(log, projectDir, io) {
14079
14154
  if (outside !== null) return { removed: false, lines: outsideLines([outside], log) };
14080
14155
  let disk;
14081
14156
  try {
14082
- disk = readFileSync26(abs, "utf8");
14157
+ disk = readFileSync27(abs, "utf8");
14083
14158
  } catch {
14084
14159
  return {
14085
14160
  removed: false,
@@ -14112,7 +14187,7 @@ function removeAnchor(log, projectDir, io) {
14112
14187
  ]
14113
14188
  };
14114
14189
  }
14115
- const backup = edited ? relative7(projectDir, backupFile(abs)).split(sep7).join("/") : null;
14190
+ const backup = edited ? relative8(projectDir, backupFile(abs)).split(sep8).join("/") : null;
14116
14191
  io.rm(abs);
14117
14192
  return {
14118
14193
  removed: true,
@@ -14124,7 +14199,7 @@ function removeAnchor(log, projectDir, io) {
14124
14199
  var AGENTS_MD2 = "AGENTS.md";
14125
14200
  function readAgentsMdTemplate(harnessRoot) {
14126
14201
  try {
14127
- return readFileSync26(join35(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
14202
+ return readFileSync27(join35(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
14128
14203
  } catch {
14129
14204
  return null;
14130
14205
  }
@@ -14157,7 +14232,7 @@ function removeUnrecordedLegacyAgentsMd(path, projectDir, io, out, pruneFrom) {
14157
14232
  const real = realpathSync3(abs);
14158
14233
  if (!statSync5(real).isFile()) return false;
14159
14234
  if (lstatSync5(abs).isSymbolicLink()) effective = real;
14160
- current = readFileSync26(effective, "utf8");
14235
+ current = readFileSync27(effective, "utf8");
14161
14236
  } catch {
14162
14237
  return false;
14163
14238
  }
@@ -14175,7 +14250,7 @@ function removeLegacyAgentsMd(path, abs, effective, strip, ctx) {
14175
14250
  let backup = `${path}.backup-<time>`;
14176
14251
  try {
14177
14252
  if (io.write) {
14178
- backup = relative7(projectDir, backupFile(effective)).split(sep7).join("/");
14253
+ backup = relative8(projectDir, backupFile(effective)).split(sep8).join("/");
14179
14254
  if (strip.remainder !== null) {
14180
14255
  writeFileSync9(effective, neutralizeScaffoldBanner(strip.remainder), "utf8");
14181
14256
  } else {
@@ -14219,9 +14294,9 @@ function removeToolFiles(files, projectDir, rm) {
14219
14294
  continue;
14220
14295
  }
14221
14296
  try {
14222
- if (hashContent(readFileSync26(abs, "utf8")) !== sha256) {
14297
+ if (hashContent(readFileSync27(abs, "utf8")) !== sha256) {
14223
14298
  const backup = backupFile(abs);
14224
- notes.push(`backed up ${path} \u2192 ${relative7(projectDir, backup)} (changed since install)`);
14299
+ notes.push(`backed up ${path} \u2192 ${relative8(projectDir, backup)} (changed since install)`);
14225
14300
  }
14226
14301
  rm(abs);
14227
14302
  removed.push(path);
@@ -14236,8 +14311,8 @@ function removeToolFiles(files, projectDir, rm) {
14236
14311
  function pruneEmptyDirsUpward(projectDir, startDir) {
14237
14312
  let cursor = startDir;
14238
14313
  while (true) {
14239
- const rel = relative7(projectDir, cursor);
14240
- if (rel === "" || rel.startsWith(`..${sep7}`) || rel === "..") return;
14314
+ const rel = relative8(projectDir, cursor);
14315
+ if (rel === "" || rel.startsWith(`..${sep8}`) || rel === "..") return;
14241
14316
  try {
14242
14317
  if (readdirSync7(cursor).length > 0) return;
14243
14318
  rmdirSync3(cursor);
@@ -14267,7 +14342,7 @@ function readRootClaudeMd(projectDir) {
14267
14342
  const path = join35(projectDir, "CLAUDE.md");
14268
14343
  if (!existsSync26(path)) return null;
14269
14344
  try {
14270
- return readFileSync26(path, "utf8");
14345
+ return readFileSync27(path, "utf8");
14271
14346
  } catch {
14272
14347
  return null;
14273
14348
  }