@uzysjung/agent-harness 26.175.0 → 26.177.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
@@ -126,12 +126,14 @@ import {
126
126
  resolveBundleRoot,
127
127
  resolveRules,
128
128
  resolveScope,
129
+ resolveWriteTarget,
129
130
  retiredMcpNames,
130
131
  scaffoldBanner,
131
132
  selectableMcpServers,
132
133
  selectedMcpServers,
133
134
  sharedPathOfKeyId,
134
135
  shouldInstallAsset,
136
+ skillFolderOf,
135
137
  skillNames,
136
138
  stripAgentsMd,
137
139
  stripHarnessImport,
@@ -144,7 +146,7 @@ import {
144
146
  withoutAppleDouble,
145
147
  wrapHarnessBlock,
146
148
  writeInstallLog
147
- } from "./chunk-44H2VEXJ.js";
149
+ } from "./chunk-JIVFWRYB.js";
148
150
 
149
151
  // node_modules/sisteransi/src/index.js
150
152
  var require_src = __commonJS({
@@ -1097,6 +1099,9 @@ var LINES = {
1097
1099
  removed: "removed",
1098
1100
  noChecksumRemoved: "no checksum on record \u2014 saved a copy, removed from live",
1099
1101
  editedRemoved: "you edited it \u2014 saved as <file>.backup-<time>, removed from live",
1102
+ // #902 — 옛 판이 파일 목록 없이 깐 외부 스킬 폴더의 파일: 소유를 몰라 되살리지 않고 백업만(update 마다 같은 판정이라 폴더 밖을 권한다)
1103
+ noFileList: "no file list on record (an older version installed this skill) \u2014 saved as <file>.backup-<time>",
1104
+ noFileListRemoved: "no file list on record (an older version installed this skill) \u2014 saved as <file>.backup-<time>, removed from live (if it was yours, keep it outside this skill folder)",
1100
1105
  sharedCreated: "wrote (harness part only)",
1101
1106
  upsert: "wrote the harness part \u2014 yours stays",
1102
1107
  strip: "removed the harness part \u2014 yours stays",
@@ -1685,7 +1690,14 @@ function withRecordedExclusions(spec, previous) {
1685
1690
  ],
1686
1691
  []
1687
1692
  );
1688
- return { spec: specWithExclusions(spec, excluded), excluded };
1693
+ const keyExclude = [...excluded].filter(isKeyId);
1694
+ return {
1695
+ spec: {
1696
+ ...specWithExclusions(spec, excluded),
1697
+ ...keyExclude.length > 0 ? { keyExclude } : {}
1698
+ },
1699
+ excluded
1700
+ };
1689
1701
  }
1690
1702
  function thisRunExclusions(spec, previous, accepts) {
1691
1703
  const excluded = /* @__PURE__ */ new Set([
@@ -1937,8 +1949,8 @@ function legacyGitignoreSeed(text, render, previous) {
1937
1949
 
1938
1950
  // src/installer.ts
1939
1951
  init_esm_shims();
1940
- import { chmodSync as chmodSync3, existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync21, writeFileSync as writeFileSync7 } from "fs";
1941
- import { basename as basename6, join as join28, relative as relative6, resolve, sep as sep7 } from "path";
1952
+ import { chmodSync as chmodSync3, existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync22, writeFileSync as writeFileSync7 } from "fs";
1953
+ import { basename as basename6, join as join30, relative as relative7, resolve, sep as sep8 } from "path";
1942
1954
 
1943
1955
  // src/anchor-seed.ts
1944
1956
  init_esm_shims();
@@ -2759,6 +2771,11 @@ function renderBundledSkill(source) {
2759
2771
  ${portBody(body)}
2760
2772
  `;
2761
2773
  }
2774
+ function renderBundledSkillFile(rel, raw) {
2775
+ if (rel === "SKILL.md") return renderBundledSkill(raw);
2776
+ return rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
2777
+ ` : raw;
2778
+ }
2762
2779
  function portBody(body) {
2763
2780
  return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
2764
2781
  }
@@ -2773,7 +2790,7 @@ function writeBundledSkillDirs(params) {
2773
2790
  }
2774
2791
  const targetDir = join13(projectDir, ".agents", "skills", id);
2775
2792
  const targetSkill = join13(targetDir, "SKILL.md");
2776
- if (!writer.write(targetSkill, renderBundledSkill(readFileSync9(srcSkill, "utf8")))) {
2793
+ if (!writer.write(targetSkill, renderBundledSkillFile("SKILL.md", readFileSync9(srcSkill, "utf8")))) {
2777
2794
  continue;
2778
2795
  }
2779
2796
  written.push(targetSkill);
@@ -2782,8 +2799,7 @@ function writeBundledSkillDirs(params) {
2782
2799
  if (rel.split("/").some((segment) => segment.startsWith("."))) continue;
2783
2800
  const segments = rel.split("/");
2784
2801
  const raw = readFileSync9(join13(srcDir, ...segments), "utf8");
2785
- const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
2786
- ` : raw;
2802
+ const ported = renderBundledSkillFile(rel, raw);
2787
2803
  const target = join13(targetDir, ...segments);
2788
2804
  if (!writer.write(target, ported, { createInRefresh: true })) continue;
2789
2805
  if (rel.endsWith(".sh")) chmodSync(target, 493);
@@ -2801,6 +2817,10 @@ trigger: glob
2801
2817
  globs: "${rule.paths.join(", ")}"
2802
2818
  ---
2803
2819
 
2820
+ `;
2821
+ }
2822
+ function renderAntigravityRule(rule) {
2823
+ return `${antigravityFrontmatter(rule)}${rule.body}
2804
2824
  `;
2805
2825
  }
2806
2826
  function runAntigravityTransform(params) {
@@ -2827,8 +2847,7 @@ function runAntigravityTransform(params) {
2827
2847
  const harnessRuleFiles = [];
2828
2848
  for (const rule of portRules(harnessRoot, rules)) {
2829
2849
  const target = join14(projectDir, ".agents", "rules", `${rule.name}.md`);
2830
- const content = `${antigravityFrontmatter(rule)}${rule.body}
2831
- `;
2850
+ const content = renderAntigravityRule(rule);
2832
2851
  if (!writer.write(target, content, { createInRefresh: antigravityInstalled })) {
2833
2852
  continue;
2834
2853
  }
@@ -3079,6 +3098,9 @@ function quoteIfNeeded(name) {
3079
3098
  // src/codex/transform.ts
3080
3099
  var HOOK_NAMES = ["session-start"];
3081
3100
  var ENV_VAR_RENAME = /CLAUDE_PROJECT_DIR/g;
3101
+ function renderCodexHook(source) {
3102
+ return source.replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
3103
+ }
3082
3104
  function runCodexTransform(params) {
3083
3105
  const {
3084
3106
  harnessRoot,
@@ -3120,7 +3142,7 @@ function runCodexTransform(params) {
3120
3142
  if (!existsSync13(src)) {
3121
3143
  continue;
3122
3144
  }
3123
- const ported = readFileSync12(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
3145
+ const ported = renderCodexHook(readFileSync12(src, "utf8"));
3124
3146
  const target = join17(hookDir, `${hook}.sh`);
3125
3147
  if (!writer.write(target, ported)) continue;
3126
3148
  chmodSync2(target, 493);
@@ -3326,6 +3348,11 @@ function trackDefaultMcpAssetIds(harnessRoot, tracks) {
3326
3348
  return id !== void 0 && anyTrack(tracks, r.pattern) ? [id] : [];
3327
3349
  });
3328
3350
  }
3351
+ function droppedMcpAssetIds(harnessRoot, excluded) {
3352
+ const rows = trackRows(harnessRoot);
3353
+ const assetOf = selectableMcpServers(rows);
3354
+ return droppedMcpServers(rows, excluded).flatMap((name) => assetOf.get(name) ?? []);
3355
+ }
3329
3356
  function trackDefaultMcpNames(harnessRoot, tracks) {
3330
3357
  const { templateMcpPath } = mcpSources(harnessRoot);
3331
3358
  const base = existsSync15(templateMcpPath) ? Object.keys(JSON.parse(readFileSync14(templateMcpPath, "utf8")).mcpServers) : [];
@@ -3534,23 +3561,148 @@ function runCliTransforms(params) {
3534
3561
 
3535
3562
  // src/displaced.ts
3536
3563
  init_esm_shims();
3537
- import { lstatSync as lstatSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync } from "fs";
3538
- import { basename as basename3, dirname as dirname5, join as join20, posix } from "path";
3564
+ import { lstatSync as lstatSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync16, renameSync } from "fs";
3565
+ import { basename as basename3, dirname as dirname5, join as join22, posix as posix3 } from "path";
3566
+
3567
+ // src/harness-bytes.ts
3568
+ init_esm_shims();
3569
+ import { lstatSync as lstatSync3, readFileSync as readFileSync15 } from "fs";
3570
+ import { join as join20, posix } from "path";
3571
+ var CLAUDE_DIR3 = ".claude/";
3572
+ function harnessBytesAt(log, path, isTarget = recordedTrackTargets(log)) {
3573
+ const norm = posix.normalize(path);
3574
+ const root = resolveBundleRoot();
3575
+ const text = (rel) => readFileSync15(join20(root, "templates", rel), "utf8");
3576
+ try {
3577
+ const rule = /^\.agents\/rules\/([^/]+)\.md$/.exec(norm)?.[1];
3578
+ if (rule !== void 0) {
3579
+ const [ported] = isTarget(`${CLAUDE_DIR3}rules/${rule}.md`) ? portRules(root, [rule]) : [];
3580
+ return ported === void 0 ? null : Buffer.from(renderAntigravityRule(ported));
3581
+ }
3582
+ const hook = /^\.codex\/hooks\/([^/]+)$/.exec(norm)?.[1];
3583
+ if (hook !== void 0) {
3584
+ return isTarget(`${CLAUDE_DIR3}hooks/${hook}`) ? Buffer.from(renderCodexHook(text(`hooks/${hook}`))) : null;
3585
+ }
3586
+ const skill = /^\.agents\/skills\/([^/]+\/.+)$/.exec(norm)?.[1];
3587
+ if (skill !== void 0) {
3588
+ return isTarget(`${CLAUDE_DIR3}skills/${skill}`) ? Buffer.from(
3589
+ renderBundledSkillFile(skill.slice(skill.indexOf("/") + 1), text(`skills/${skill}`))
3590
+ ) : null;
3591
+ }
3592
+ if (norm.startsWith(CLAUDE_DIR3) && isTarget(norm)) {
3593
+ return readFileSync15(join20(root, "templates", norm.slice(CLAUDE_DIR3.length)));
3594
+ }
3595
+ } catch {
3596
+ }
3597
+ return null;
3598
+ }
3599
+ function holdsHarnessBytes(log, projectDir, path, isTarget = recordedTrackTargets(log)) {
3600
+ const slot = join20(projectDir, path);
3601
+ try {
3602
+ if (!lstatSync3(slot).isFile()) return false;
3603
+ const disk = readFileSync15(slot);
3604
+ const bundle = harnessBytesAt(log, path, isTarget);
3605
+ if (bundle !== null && disk.equals(bundle)) return true;
3606
+ const rec = recorded(log, path, { isTarget });
3607
+ return rec.state === "sha" && rec.sha256 === hashContent(disk.toString("utf8"));
3608
+ } catch {
3609
+ return false;
3610
+ }
3611
+ }
3612
+
3613
+ // src/harness-slot.ts
3614
+ init_esm_shims();
3615
+ import { isAbsolute, join as join21, posix as posix2, relative as relative4, sep as sep5 } from "path";
3616
+ var ownedOf = (...kinds) => [
3617
+ ...new Set(
3618
+ Object.values(CLI_OWNERSHIP).flat().filter((o) => kinds.includes(o.kind)).map((o) => o.path)
3619
+ )
3620
+ ];
3621
+ var HARNESS_FILES = {
3622
+ places: [
3623
+ ...ownedOf("recorded", "anchor"),
3624
+ ...buildManifest({ tracks: [] }).map((e2) => e2.target).filter(isCliNeutralTarget)
3625
+ ],
3626
+ except: new Set(Object.keys(SHARED_FILES))
3627
+ };
3628
+ var ANCHOR_FILES = { places: ownedOf("anchor", "import-block") };
3629
+ function skillFolders(roots) {
3630
+ return { places: roots.map((r) => `${r}/`) };
3631
+ }
3632
+ function within(rel, slots) {
3633
+ if (slots.except?.has(rel) === true) return false;
3634
+ return slots.places.some((p2) => p2.endsWith("/") ? rel.startsWith(p2) : rel === p2);
3635
+ }
3636
+ function realRel(projectDir, norm, follow) {
3637
+ const target = follow ? resolveWriteTarget(join21(projectDir, norm)) : (() => {
3638
+ const dir = resolveWriteTarget(join21(projectDir, posix2.dirname(norm)));
3639
+ return dir === null ? null : join21(dir, posix2.basename(norm));
3640
+ })();
3641
+ if (target === null) return null;
3642
+ const rel = relative4(resolveWriteTarget(projectDir) ?? projectDir, target);
3643
+ if (rel === "" || isAbsolute(rel)) return null;
3644
+ const slash = rel.split(sep5).join("/");
3645
+ return slash === ".." || slash.startsWith("../") ? null : slash;
3646
+ }
3647
+ function inSlotByName(path, slots = HARNESS_FILES) {
3648
+ return within(posix2.normalize(path), slots);
3649
+ }
3650
+ function inHarnessSlot(projectDir, path, slots = HARNESS_FILES, opts = {}) {
3651
+ const norm = posix2.normalize(path);
3652
+ if (!within(norm, slots)) return false;
3653
+ const real = realRel(projectDir, norm, opts.follow === true);
3654
+ return real !== null && within(real, slots);
3655
+ }
3656
+
3657
+ // src/screen-text.ts
3658
+ init_esm_shims();
3659
+ function screenText(text) {
3660
+ return text.replace(/[^\p{L}\p{N}\p{M}._\-/~+@:,=%]/gu, (ch, at2) => {
3661
+ if (ch === " " && at2 > 0 && at2 < text.length - 1 && text[at2 - 1] !== " " && text[at2 + 1] !== " ") {
3662
+ return ch;
3663
+ }
3664
+ return asCode(ch);
3665
+ });
3666
+ }
3667
+ function oneLine(text) {
3668
+ return text.replace(/[\p{Cc}\u2028\u2029]/gu, asCode);
3669
+ }
3670
+ function asCode(ch) {
3671
+ const cp = ch.codePointAt(0);
3672
+ return cp > 65535 ? `\\u{${cp.toString(16)}}` : `\\u${cp.toString(16).padStart(4, "0")}`;
3673
+ }
3674
+
3675
+ // src/displaced.ts
3539
3676
  function exists(abs) {
3540
3677
  try {
3541
- lstatSync3(abs);
3678
+ lstatSync4(abs);
3542
3679
  return true;
3543
3680
  } catch {
3544
3681
  return false;
3545
3682
  }
3546
3683
  }
3547
3684
  function outsideBackup(projectDir, backup) {
3548
- const abs = join20(projectDir, backup);
3685
+ const abs = join22(projectDir, backup);
3549
3686
  const hit = outsideProjectTarget(projectDir, dirname5(abs));
3550
- return hit === null ? null : join20(hit.target, basename3(abs));
3687
+ return hit === null ? null : join22(hit.target, basename3(abs));
3551
3688
  }
3552
3689
  function isBackupOf(path, backup) {
3553
- return backupOriginal(posix.normalize(backup)) === posix.normalize(path);
3690
+ return backupOriginal(posix3.normalize(backup)) === posix3.normalize(path);
3691
+ }
3692
+ function replacesOnlyHarnessCopy(log, projectDir, path, isTarget) {
3693
+ if (harnessBytesAt(log, path, isTarget) === null) return false;
3694
+ return !exists(join22(projectDir, path)) || holdsHarnessBytes(log, projectDir, path, isTarget);
3695
+ }
3696
+ function handBackOf(log, projectDir, f, isTarget) {
3697
+ const backup = f.notes[0];
3698
+ if (backup === void 0 || !inSlotByName(f.path)) return null;
3699
+ if (outsideBackup(projectDir, backup) !== null || !isBackupOf(f.path, backup)) return null;
3700
+ if (!exists(join22(projectDir, backup)) || isLegacyDisplaced(log, projectDir, f)) return null;
3701
+ return {
3702
+ from: posix3.normalize(backup),
3703
+ to: posix3.normalize(f.path),
3704
+ safe: replacesOnlyHarnessCopy(log, projectDir, f.path, isTarget)
3705
+ };
3554
3706
  }
3555
3707
  function isLegacyDisplaced(log, projectDir, f) {
3556
3708
  if (f.change !== "displaced") return false;
@@ -3558,7 +3710,7 @@ function isLegacyDisplaced(log, projectDir, f) {
3558
3710
  if (outsideBackup(projectDir, at2) !== null) return false;
3559
3711
  let original;
3560
3712
  try {
3561
- original = readFileSync15(join20(projectDir, at2), "utf8");
3713
+ original = readFileSync16(join22(projectDir, at2), "utf8");
3562
3714
  } catch {
3563
3715
  return false;
3564
3716
  }
@@ -3566,10 +3718,24 @@ function isLegacyDisplaced(log, projectDir, f) {
3566
3718
  }
3567
3719
  function restoreDisplaced(log, projectDir, opts) {
3568
3720
  const out = [];
3721
+ let bundleTargets;
3569
3722
  for (const f of log.rootFiles ?? []) {
3570
3723
  if (f.change !== "displaced" || !opts.has(f.path)) continue;
3571
- const slot = join20(projectDir, f.path);
3724
+ const slot = join22(projectDir, f.path);
3572
3725
  if (outsideProjectTarget(projectDir, slot) !== null) continue;
3726
+ if (!inHarnessSlot(projectDir, f.path)) {
3727
+ bundleTargets ??= recordedTrackTargets(log);
3728
+ const handBack = handBackOf(log, projectDir, f, bundleTargets);
3729
+ out.push({
3730
+ path: f.path,
3731
+ backup: f.notes[0] ?? null,
3732
+ verdict: "advise",
3733
+ line: "not put back \u2014 the install record says this tool moved a file of yours aside here, but this is (or leads) outside the places this tool installs to; left it as is",
3734
+ forget: true,
3735
+ ...handBack === null ? {} : { handBack }
3736
+ });
3737
+ continue;
3738
+ }
3573
3739
  const backup = isLegacyDisplaced(log, projectDir, f) ? null : f.notes[0] ?? null;
3574
3740
  const away = backup === null ? null : outsideBackup(projectDir, backup);
3575
3741
  if (away !== null) {
@@ -3577,7 +3743,7 @@ function restoreDisplaced(log, projectDir, opts) {
3577
3743
  path: f.path,
3578
3744
  backup,
3579
3745
  verdict: "advise",
3580
- line: `not put back \u2014 the install record points its backup outside this project (${away}); left that file where it is`,
3746
+ line: `not put back \u2014 the install record points its backup outside this project (${screenText(away)}); left that file where it is`,
3581
3747
  forget: true
3582
3748
  });
3583
3749
  continue;
@@ -3587,19 +3753,19 @@ function restoreDisplaced(log, projectDir, opts) {
3587
3753
  path: f.path,
3588
3754
  backup,
3589
3755
  verdict: "advise",
3590
- line: `not put back \u2014 the install record names ${JSON.stringify(backup)} as the backup, which is not a backup this tool made for this file; left that file where it is`,
3756
+ line: `not put back \u2014 the install record names "${screenText(backup)}" as the backup, which is not a backup this tool made for this file; left that file where it is`,
3591
3757
  forget: true
3592
3758
  });
3593
3759
  continue;
3594
3760
  }
3595
3761
  const d2 = judgeDisplaced({
3596
3762
  backup,
3597
- backupExists: backup !== null && exists(join20(projectDir, backup)),
3763
+ backupExists: backup !== null && exists(join22(projectDir, backup)),
3598
3764
  slotEmpty: !exists(slot) || !opts.write && opts.willBeGone?.has(f.path) === true
3599
3765
  });
3600
3766
  if (opts.write && d2.verdict === "restore" && backup !== null) {
3601
3767
  mkdirSync5(dirname5(slot), { recursive: true });
3602
- renameSync(join20(projectDir, backup), slot);
3768
+ renameSync(join22(projectDir, backup), slot);
3603
3769
  }
3604
3770
  out.push({
3605
3771
  path: f.path,
@@ -3619,7 +3785,7 @@ function withoutRestored(rootFiles, outcomes) {
3619
3785
  // src/excluded-still-there.ts
3620
3786
  init_esm_shims();
3621
3787
  import { existsSync as existsSync16 } from "fs";
3622
- import { join as join21 } from "path";
3788
+ import { join as join23 } from "path";
3623
3789
  function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
3624
3790
  const out = [];
3625
3791
  const add = (id, catalog) => {
@@ -3632,7 +3798,7 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
3632
3798
  for (const id of excluded) {
3633
3799
  if (INTERNAL_BUNDLED_SKILL_IDS.includes(id)) {
3634
3800
  const there = [".claude/skills", ".agents/skills"].some(
3635
- (d2) => existsSync16(join21(projectDir, d2, id))
3801
+ (d2) => existsSync16(join23(projectDir, d2, id))
3636
3802
  );
3637
3803
  if (there) add(id, false);
3638
3804
  } else {
@@ -3645,10 +3811,13 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
3645
3811
  }
3646
3812
  function assetStillThere(projectDir, asset) {
3647
3813
  if (asset.method !== "skill" || asset.scope === "global") return true;
3648
- if (asset.files !== void 0)
3649
- return asset.files.some((f) => existsSync16(join21(projectDir, f.path)));
3814
+ if (asset.files?.some((f) => existsSync16(join23(projectDir, f.path)))) return true;
3815
+ if (asset.files !== void 0 && asset.filesPartial !== true) return false;
3650
3816
  const dir = asset.detail.skill ?? asset.id;
3651
- return [".claude/skills", ".agents/skills"].some((d2) => existsSync16(join21(projectDir, d2, dir)));
3817
+ const listed = new Set((asset.files ?? []).map((f) => f.path.split("/").slice(0, 3).join("/")));
3818
+ return [".claude/skills", ".agents/skills"].some(
3819
+ (d2) => !listed.has(`${d2}/${dir}`) && existsSync16(join23(projectDir, d2, dir))
3820
+ );
3652
3821
  }
3653
3822
 
3654
3823
  // src/external-installer.ts
@@ -3658,10 +3827,10 @@ import {
3658
3827
  accessSync,
3659
3828
  existsSync as existsSync17,
3660
3829
  constants as fsConstants,
3661
- lstatSync as lstatSync4,
3830
+ lstatSync as lstatSync5,
3662
3831
  mkdirSync as mkdirSync6,
3663
3832
  readdirSync as readdirSync3,
3664
- readFileSync as readFileSync16,
3833
+ readFileSync as readFileSync17,
3665
3834
  readlinkSync,
3666
3835
  realpathSync as realpathSync3,
3667
3836
  renameSync as renameSync2,
@@ -3669,7 +3838,10 @@ import {
3669
3838
  rmSync as rmSync3
3670
3839
  } from "fs";
3671
3840
  import { homedir as homedir2 } from "os";
3672
- import { basename as basename4, dirname as dirname6, join as join22, relative as relative4, sep as sep5 } from "path";
3841
+ import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute2, join as join24, relative as relative5, sep as sep6 } from "path";
3842
+ function outsideSlotNote(o) {
3843
+ return o.slot !== void 0 ? `${o.slot}/ left as is (links outside the skill folders: ${o.target})` : `${o.root}/ left as is (links outside the project: ${o.target})`;
3844
+ }
3673
3845
  var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
3674
3846
  function skillRootOf(cli) {
3675
3847
  return cli === "claude" ? ".claude/skills" : ".agents/skills";
@@ -3677,11 +3849,11 @@ function skillRootOf(cli) {
3677
3849
  function snapshotSkillRoots(projectDir) {
3678
3850
  const out = /* @__PURE__ */ new Map();
3679
3851
  for (const root of SKILL_ROOTS) {
3680
- if (outsideProjectTarget(projectDir, join22(projectDir, root)) !== null) continue;
3681
- for (const rel of listFilesRecursive(join22(projectDir, root))) {
3852
+ if (outsideProjectTarget(projectDir, join24(projectDir, root)) !== null) continue;
3853
+ for (const rel of listFilesRecursive(join24(projectDir, root))) {
3682
3854
  const path = `${root}/${rel}`;
3683
3855
  try {
3684
- out.set(path, hashContent(readFileSync16(join22(projectDir, path), "utf8")));
3856
+ out.set(path, hashContent(readFileSync17(join24(projectDir, path), "utf8")));
3685
3857
  } catch {
3686
3858
  }
3687
3859
  }
@@ -3699,8 +3871,8 @@ function homeSkillDirs(roots) {
3699
3871
  }
3700
3872
  for (const name of names) {
3701
3873
  try {
3702
- const st2 = lstatSync4(join22(root, name), { bigint: true });
3703
- if (st2.isDirectory()) out.set(join22(root, name), `${st2.ino}:${st2.ctimeNs}`);
3874
+ const st2 = lstatSync5(join24(root, name), { bigint: true });
3875
+ if (st2.isDirectory()) out.set(join24(root, name), `${st2.ino}:${st2.ctimeNs}`);
3704
3876
  } catch {
3705
3877
  }
3706
3878
  }
@@ -3713,9 +3885,9 @@ function writtenSkillNames(before, after) {
3713
3885
  }
3714
3886
  function lockedSkillNames(source) {
3715
3887
  const xdg = process.env.XDG_STATE_HOME;
3716
- const path = xdg ? join22(xdg, "skills", ".skill-lock.json") : join22(homedir2(), ".agents", ".skill-lock.json");
3888
+ const path = xdg ? join24(xdg, "skills", ".skill-lock.json") : join24(homedir2(), ".agents", ".skill-lock.json");
3717
3889
  try {
3718
- const lock = JSON.parse(readFileSync16(path, "utf8"));
3890
+ const lock = JSON.parse(readFileSync17(path, "utf8"));
3719
3891
  if (typeof lock.version !== "number" || lock.version < 3) return null;
3720
3892
  if (typeof lock.skills !== "object" || lock.skills === null) return null;
3721
3893
  const out = /* @__PURE__ */ new Set();
@@ -3815,18 +3987,30 @@ function installOne(asset, ctx) {
3815
3987
  const written = writtenSkillNames(before2, homeSkillDirs(roots));
3816
3988
  return { ...result2, skills: written.filter((n) => locked.has(n)) };
3817
3989
  }
3818
- const outside = SKILL_ROOTS.flatMap((root) => {
3819
- const hit = outsideProjectTarget(cwd, join22(cwd, root));
3820
- return hit === null ? [] : [{ root, target: hit.target }];
3821
- });
3990
+ const outside = SKILL_ROOTS.flatMap(
3991
+ (root) => {
3992
+ const hit = outsideProjectTarget(cwd, join24(cwd, root));
3993
+ if (hit !== null) return [{ root, target: hit.target }];
3994
+ const slot = method.skill === void 0 ? root : `${root}/${method.skill}`;
3995
+ if (inSkillFolders(cwd, slot)) return [];
3996
+ return [{ root, target: resolveWriteTarget(join24(cwd, slot)) ?? slot, slot }];
3997
+ }
3998
+ );
3822
3999
  const outRoots = new Set(outside.map((o) => o.root));
3823
4000
  const cli = ctx.cli.filter((c3) => !outRoots.has(skillRootOf(c3)));
3824
4001
  if (outside.length > 0 && cli.length === 0) {
3825
- const where = outside.map((o) => `${o.root} \u2192 ${o.target}`).join(", ");
4002
+ const away = outside.filter((o) => o.slot === void 0);
4003
+ const where = away.map((o) => `${o.root} \u2192 ${o.target}`).join(", ");
4004
+ const reasons = [
4005
+ ...away.length > 0 ? [`the skill folder links outside the project (${where})`] : [],
4006
+ ...outside.flatMap(
4007
+ (o) => o.slot === void 0 ? [] : [`${o.slot} links outside the skill folders (${o.target})`]
4008
+ )
4009
+ ];
3826
4010
  return {
3827
4011
  asset,
3828
4012
  ok: false,
3829
- message: `not installed \u2014 the skill folder links outside the project (${where}); left as is`,
4013
+ message: `not installed \u2014 ${reasons.join("; ")}; left as is`,
3830
4014
  outside
3831
4015
  };
3832
4016
  }
@@ -3898,16 +4082,20 @@ function refreshExternalSkills(projectDir, deps = {}) {
3898
4082
  if (targets.length === 0) {
3899
4083
  return { ...none, notInCatalog, unknown: false, ...copies };
3900
4084
  }
4085
+ const scopeOf = (a) => resolveScope(log.assets.find((r) => r.id === a.id)?.scope ?? log.scope);
3901
4086
  const targetIds = new Set(targets.map((a) => a.id));
3902
4087
  const clis = [...installedClis(log)];
4088
+ const inProject = targets.filter((a) => scopeOf(a) === "project");
4089
+ const slotCtx = { log, scope: "project" };
3903
4090
  const settle = holdSkillSlots(projectDir, {
3904
4091
  recorded: log.assets.filter((a) => targetIds.has(a.id)),
3905
- slots: skillSlotsFor(targets, clis, { log, scope: log.scope }),
4092
+ assets: targets,
4093
+ slots: skillSlotsFor(inProject, clis, slotCtx),
4094
+ unlisted: unlistedSkillSlots(inProject, clis, slotCtx),
3906
4095
  log,
3907
4096
  clis,
3908
- scope: log.scope
4097
+ scope: "project"
3909
4098
  });
3910
- const scopeOf = (a) => resolveScope(log.assets.find((r) => r.id === a.id)?.scope ?? log.scope);
3911
4099
  const run = deps.run ?? runExternalInstall;
3912
4100
  let ran;
3913
4101
  try {
@@ -3956,6 +4144,9 @@ function refreshExternalSkills(projectDir, deps = {}) {
3956
4144
  );
3957
4145
  const late = reclaimNewlyNamed(projectDir, log, next);
3958
4146
  const reclaimedAll = [...reclaimed, ...late];
4147
+ const leftAsIs = report.attempted.flatMap(
4148
+ (r) => r.ok && r.outside !== void 0 && r.outside.length > 0 ? [{ id: r.asset.id, outside: r.outside }] : []
4149
+ );
3959
4150
  return {
3960
4151
  attempted: report.attempted.length,
3961
4152
  refreshed: report.succeeded,
@@ -3963,6 +4154,7 @@ function refreshExternalSkills(projectDir, deps = {}) {
3963
4154
  notInCatalog,
3964
4155
  unknown: false,
3965
4156
  ...reclaimedAll.length > 0 ? { reclaimed: reclaimedAll } : {},
4157
+ ...leftAsIs.length > 0 ? { leftAsIs } : {},
3966
4158
  judged: [...reclaimedAll.flatMap((r) => r.judged), ...judged]
3967
4159
  };
3968
4160
  }
@@ -3970,15 +4162,16 @@ function holdSkillSlots(projectDir, hold) {
3970
4162
  const now = /* @__PURE__ */ new Date();
3971
4163
  const asides = /* @__PURE__ */ new Map();
3972
4164
  const moved = [];
4165
+ const kept = slotFilesToKeep(projectDir, hold);
3973
4166
  try {
3974
- for (const f of slotFilesToKeep(projectDir, hold)) {
4167
+ for (const f of kept.files) {
3975
4168
  const root = dirname6(f.slot);
3976
4169
  let dir = asides.get(root);
3977
4170
  if (dir === void 0) {
3978
- dir = claimBackupDir(join22(projectDir, root), now);
4171
+ dir = claimBackupDir(join24(projectDir, root), now);
3979
4172
  asides.set(root, dir);
3980
4173
  }
3981
- const aside = join22(dir, f.rel.slice(root.length + 1));
4174
+ const aside = join24(dir, f.rel.slice(root.length + 1));
3982
4175
  mkdirSync6(dirname6(aside), { recursive: true });
3983
4176
  renameSync2(f.abs, aside);
3984
4177
  moved.push({ ...f, aside });
@@ -4001,24 +4194,42 @@ function holdSkillSlots(projectDir, hold) {
4001
4194
  displaced: [],
4002
4195
  judged: []
4003
4196
  };
4197
+ const ok = (report?.attempted ?? []).filter((r) => r.ok);
4198
+ const rewritten = new Set(
4199
+ [
4200
+ ...ok.flatMap((r) => (r.files?.created ?? []).map((x) => skillFolderOf(x.path))),
4201
+ ...skillSlotsFor(
4202
+ ok.map((r) => r.asset),
4203
+ hold.clis,
4204
+ { log: hold.log, scope: hold.scope }
4205
+ )
4206
+ ].flatMap((p2) => realOrNull(join24(projectDir, p2)) ?? [])
4207
+ );
4004
4208
  const back = /* @__PURE__ */ new Set();
4005
4209
  const failed = [];
4006
4210
  for (const f of moved) {
4007
4211
  const restore = failedSlots === null || failedSlots.some((s) => f.rel === s || f.rel.startsWith(`${s}/`));
4008
4212
  try {
4009
- const r = settleAside(projectDir, f, restore);
4213
+ const literal = realFileOrNull(f.abs) === f.real;
4214
+ const at2 = literal ? f : { ...f, abs: inProjectSpelling(projectDir, f.real) };
4215
+ const r = settleAside(
4216
+ projectDir,
4217
+ at2,
4218
+ restore,
4219
+ f.folder !== null && rewritten.has(f.folder) && inCanonicalSkillRoot(projectDir, f.real)
4220
+ );
4010
4221
  if (r === "back") {
4011
- back.add(f.rel);
4222
+ if (literal) back.add(f.rel);
4012
4223
  continue;
4013
4224
  }
4014
4225
  out.backups.push(r.backup);
4015
4226
  if (r.displaced !== null) out.displaced.push(r.displaced);
4016
4227
  const backup = toProjectRel(projectDir, r.backup);
4017
- const line = !f.recorded ? LINES.hadFile : r.gone ? LINES.editedRemoved : LINES.edited;
4228
+ const line = f.unlisted ? r.gone ? LINES.noFileListRemoved : LINES.noFileList : !f.recorded ? LINES.hadFile : r.gone ? LINES.editedRemoved : LINES.edited;
4018
4229
  out.judged.push({
4019
4230
  path: f.rel,
4020
4231
  verdict: r.gone ? "backup+remove" : "backup+overwrite",
4021
- recorded: f.recorded ? "sha" : "none",
4232
+ recorded: f.recorded ? "sha" : f.unlisted ? "no-sha" : "none",
4022
4233
  line: withBackup(line, backup),
4023
4234
  backup,
4024
4235
  backupAbs: r.backup
@@ -4034,22 +4245,52 @@ function holdSkillSlots(projectDir, hold) {
4034
4245
  `could not put back ${failed.length} file(s) moved aside before the skill tool ran \u2014 they stay in ${at2}: ${failed.join(", ")}`
4035
4246
  );
4036
4247
  }
4037
- return { ...out, report: report === null ? null : withoutCreated(report, back) };
4248
+ const settled = report === null ? null : settledReport(report, back, coveredBy(report, kept, hold));
4249
+ return { ...out, report: settled };
4038
4250
  };
4039
4251
  }
4252
+ function coveredBy(report, kept, hold) {
4253
+ const unlisted = new Set(hold.unlisted ?? []);
4254
+ const out = /* @__PURE__ */ new Map();
4255
+ if (unlisted.size === 0) return out;
4256
+ for (const r of report.attempted) {
4257
+ if (!r.ok || r.files === void 0) continue;
4258
+ const own = new Set(
4259
+ skillSlotsFor([r.asset], hold.clis, { log: hold.log, scope: hold.scope }).filter(
4260
+ (s) => unlisted.has(s)
4261
+ )
4262
+ );
4263
+ const folders = /* @__PURE__ */ new Set();
4264
+ for (const f of r.files.created) {
4265
+ const folder = skillFolderOf(f.path);
4266
+ if (own.has(kept.ownerOf(f.path)) && !kept.left.has(folder)) folders.add(folder);
4267
+ }
4268
+ if (folders.size > 0) out.set(r, folders);
4269
+ }
4270
+ return out;
4271
+ }
4040
4272
  function skillSlotsFor(assets, clis, ctx) {
4041
4273
  if (resolveScope(ctx.scope) === "global") return [];
4042
4274
  const roots = clis.length === 0 ? [...SKILL_ROOTS] : [...new Set(clis.map(skillRootOf))];
4043
4275
  const out = /* @__PURE__ */ new Set();
4044
4276
  for (const a of assets) {
4045
4277
  if (a.method.kind !== "skill") continue;
4046
- const prior = ctx.log?.assets.find((r) => r.id === a.id);
4047
- if (prior !== void 0 && prior.files === void 0) continue;
4048
4278
  const skill = a.method.skill;
4049
4279
  for (const root of roots) out.add(skill === void 0 ? root : `${root}/${skill}`);
4050
4280
  }
4051
4281
  return [...out];
4052
4282
  }
4283
+ function unlistedSkillSlots(assets, clis, ctx) {
4284
+ const out = assets.flatMap((a) => {
4285
+ const prior = ctx.log?.assets.find((r) => r.id === a.id);
4286
+ if (prior === void 0) return [];
4287
+ if (prior.files === void 0) return skillSlotsFor([a], clis, ctx);
4288
+ if (prior.filesPartial !== true) return [];
4289
+ const listed = new Set(prior.files.map((f) => skillFolderOf(f.path)));
4290
+ return skillSlotsFor([a], clis, ctx).filter((s) => !listed.has(s));
4291
+ });
4292
+ return [...new Set(out)];
4293
+ }
4053
4294
  function reclaimGlobalSkillCopies(projectDir, log, opts) {
4054
4295
  const clis = installedClis(log);
4055
4296
  const roots = clis.length === 0 ? [...SKILL_ROOTS] : [...new Set(clis.map(skillRootOf))];
@@ -4062,17 +4303,19 @@ function reclaimGlobalSkillCopies(projectDir, log, opts) {
4062
4303
  if (a.method !== "skill" || a.scope !== "global" || excluded.has(a.id)) continue;
4063
4304
  if (opts.ids !== void 0 && !opts.ids.has(a.id)) continue;
4064
4305
  const skill = a.detail.skill;
4065
- const names = skill !== void 0 ? [skill] : (Array.isArray(a.skills) ? a.skills : []).filter(isSkillName);
4306
+ const names = (skill !== void 0 ? [skill] : Array.isArray(a.skills) ? a.skills : []).filter(
4307
+ isSkillName
4308
+ );
4066
4309
  for (const name of names) {
4067
4310
  for (const root of roots) {
4068
4311
  const slot = `${root}/${name}`;
4069
- const real = realOrNull(join22(projectDir, slot));
4312
+ const real = realOrNull(join24(projectDir, slot));
4070
4313
  if (real !== null) {
4071
4314
  if (seen.has(real)) continue;
4072
4315
  seen.add(real);
4073
4316
  }
4074
4317
  const ctx = { log, others, write: opts.write, rm, id: a.id };
4075
- const r = reclaimCopy(projectDir, slot, join22(globalSkillRoot(root), name), ctx);
4318
+ const r = reclaimCopy(projectDir, slot, join24(globalSkillRoot(root), name), ctx);
4076
4319
  if (r !== null) out.push(r);
4077
4320
  }
4078
4321
  }
@@ -4081,24 +4324,31 @@ function reclaimGlobalSkillCopies(projectDir, log, opts) {
4081
4324
  }
4082
4325
  function globalSkillRoot(root) {
4083
4326
  if (root === ".claude/skills") {
4084
- return join22(process.env.CLAUDE_CONFIG_DIR?.trim() || join22(homedir2(), ".claude"), "skills");
4327
+ return join24(process.env.CLAUDE_CONFIG_DIR?.trim() || join24(homedir2(), ".claude"), "skills");
4085
4328
  }
4086
- return join22(homedir2(), ".agents/skills");
4329
+ return join24(homedir2(), ".agents/skills");
4087
4330
  }
4088
4331
  function isSkillName(name) {
4089
4332
  return typeof name === "string" && name !== "" && name !== "." && name !== ".." && !/[\\/]/.test(name);
4090
4333
  }
4091
4334
  function reclaimCopy(projectDir, slot, home, ctx) {
4092
- const abs = join22(projectDir, slot);
4335
+ const abs = join24(projectDir, slot);
4093
4336
  try {
4094
- if (lstatSync4(abs, { throwIfNoEntry: false })?.isDirectory() !== true) return null;
4337
+ if (lstatSync5(abs, { throwIfNoEntry: false })?.isDirectory() !== true) return null;
4095
4338
  if (outsideProjectTarget(projectDir, abs) !== null) return null;
4096
4339
  if (existsSync17(home) && realpathSync3(abs) === realpathSync3(home)) return null;
4097
4340
  } catch {
4098
4341
  return null;
4099
4342
  }
4100
- const homeExists = existsSync17(home);
4101
4343
  const res = { id: ctx.id, slot, removed: [], judged: [], left: [] };
4344
+ if (!inHarnessSlot(projectDir, slot, skillFolders(SKILL_ROOTS))) {
4345
+ res.left.push({
4346
+ path: slot,
4347
+ why: "it leads outside the skill folders this tool installs to \u2014 not touched"
4348
+ });
4349
+ return res;
4350
+ }
4351
+ const homeExists = existsSync17(home);
4102
4352
  const now = /* @__PURE__ */ new Date();
4103
4353
  let entries;
4104
4354
  try {
@@ -4115,15 +4365,15 @@ function reclaimCopy(projectDir, slot, home, ctx) {
4115
4365
  res.left.push({ path, why: "a link \u2014 not part of the copy the tool put here" });
4116
4366
  continue;
4117
4367
  }
4118
- const file = join22(abs, rel);
4368
+ const file = join24(abs, rel);
4119
4369
  try {
4120
- const mine = readFileSync16(file);
4121
- const same = homeExists && readOrNull(join22(home, rel))?.equals(mine) === true;
4370
+ const mine = readFileSync17(file);
4371
+ const same = homeExists && readOrNull(join24(home, rel))?.equals(mine) === true;
4122
4372
  const backupAbs = !same && ctx.write ? backupFile(file, now) : void 0;
4123
4373
  if (ctx.write) ctx.rm(file);
4124
4374
  res.removed.push(path);
4125
4375
  if (same) continue;
4126
- const line = homeExists ? `not the same as the global copy (${join22(home, rel)}) \u2014 saved as <file>.backup-<time>, removed from live` : `no global copy at ${home} to compare \u2014 saved as <file>.backup-<time>, removed from live`;
4376
+ const line = homeExists ? `not the same as the global copy (${join24(home, rel)}) \u2014 saved as <file>.backup-<time>, removed from live` : `no global copy at ${home} to compare \u2014 saved as <file>.backup-<time>, removed from live`;
4127
4377
  const judged = {
4128
4378
  path,
4129
4379
  verdict: "backup+remove",
@@ -4142,6 +4392,14 @@ function reclaimCopy(projectDir, slot, home, ctx) {
4142
4392
  if (ctx.write && res.removed.length > 0) pruneEmptyDirs(abs);
4143
4393
  return res.removed.length > 0 || res.left.length > 0 ? res : null;
4144
4394
  }
4395
+ function inCanonicalSkillRoot(projectDir, real) {
4396
+ const rel = relative5(realOrNull(projectDir) ?? projectDir, real).split(sep6).join("/");
4397
+ return SKILL_ROOTS.some((r) => rel.startsWith(`${r}/`));
4398
+ }
4399
+ function inProjectSpelling(projectDir, real) {
4400
+ const at2 = relative5(realOrNull(projectDir) ?? projectDir, real);
4401
+ return at2 === "" || at2 === ".." || at2.startsWith(`..${sep6}`) || isAbsolute2(at2) ? real : join24(projectDir, at2);
4402
+ }
4145
4403
  function realOrNull(abs) {
4146
4404
  try {
4147
4405
  return realpathSync3(abs);
@@ -4151,11 +4409,11 @@ function realOrNull(abs) {
4151
4409
  }
4152
4410
  function realFileOrNull(abs) {
4153
4411
  const dir = realOrNull(dirname6(abs));
4154
- return dir === null ? null : join22(dir, basename4(abs));
4412
+ return dir === null ? null : join24(dir, basename4(abs));
4155
4413
  }
4156
4414
  function readOrNull(abs) {
4157
4415
  try {
4158
- return readFileSync16(abs);
4416
+ return readFileSync17(abs);
4159
4417
  } catch {
4160
4418
  return null;
4161
4419
  }
@@ -4168,7 +4426,7 @@ function slotFilesToKeep(projectDir, hold) {
4168
4426
  const sha = new Map(mine.map((f) => [f.path, f.sha256]));
4169
4427
  const realSha = new Map(
4170
4428
  mine.flatMap((f) => {
4171
- const real = realFileOrNull(join22(projectDir, f.path));
4429
+ const real = realFileOrNull(join24(projectDir, f.path));
4172
4430
  return real === null ? [] : [[real, f.sha256]];
4173
4431
  })
4174
4432
  );
@@ -4177,49 +4435,113 @@ function slotFilesToKeep(projectDir, hold) {
4177
4435
  (hold.log?.assets ?? []).filter((a) => !targetIds.has(a.id)).flatMap((a) => (a.files ?? []).map((f) => f.path))
4178
4436
  );
4179
4437
  const slots = /* @__PURE__ */ new Set([
4180
- ...mine.map((f) => f.path.split("/")).filter((parts) => parts.length > 3).map((parts) => parts.slice(0, 3).join("/")),
4438
+ ...hold.recorded.flatMap(
4439
+ (a) => recordedSlots(
4440
+ projectDir,
4441
+ a,
4442
+ hold.assets.find((c3) => c3.id === a.id)
4443
+ )
4444
+ ),
4181
4445
  ...hold.slots
4182
4446
  ]);
4447
+ const unlisted = new Set(hold.unlisted ?? []);
4448
+ const ownerOf = (path) => {
4449
+ let owner = "";
4450
+ for (const s of slots) if (path.startsWith(`${s}/`) && s.length > owner.length) owner = s;
4451
+ return owner;
4452
+ };
4453
+ const unlistedAt = (path) => unlisted.has(ownerOf(path));
4183
4454
  const out = /* @__PURE__ */ new Map();
4455
+ const left = /* @__PURE__ */ new Set();
4184
4456
  const realProject = realpathSync3(projectDir);
4185
4457
  for (const slot of slots) {
4186
- if (outsideProjectTarget(projectDir, join22(projectDir, slot)) !== null) continue;
4187
- for (const { rel, link } of listEntriesRecursive(join22(projectDir, slot))) {
4458
+ if (outsideProjectTarget(projectDir, join24(projectDir, slot)) !== null) continue;
4459
+ if (!inSkillFolders(projectDir, slot)) continue;
4460
+ for (const { rel, link } of listEntriesRecursive(join24(projectDir, slot))) {
4188
4461
  const path = `${slot}/${rel}`;
4189
- if (link && SKILL_ROOTS.includes(dirname6(path))) continue;
4190
- const abs = join22(projectDir, path);
4191
- const real = join22(realpathSync3(dirname6(abs)), basename4(abs));
4462
+ if (link && SKILL_ROOTS.includes(dirname6(path))) {
4463
+ left.add(skillFolderOf(path));
4464
+ continue;
4465
+ }
4466
+ const abs = join24(projectDir, path);
4467
+ const real = join24(realpathSync3(dirname6(abs)), basename4(abs));
4192
4468
  const prev = out.get(real);
4193
- if (prev !== void 0 && (prev.rel === path || real !== join22(realProject, path))) continue;
4469
+ if (prev !== void 0 && (prev.rel === path || real !== join24(realProject, path))) continue;
4194
4470
  const recordedSha = sha.get(path) ?? realSha.get(real);
4195
- if (recordedSha === void 0 && (others.has(path) || recorded(hold.log, path).state !== "none"))
4471
+ if (recordedSha === void 0 && (others.has(path) || recorded(hold.log, path).state !== "none")) {
4472
+ left.add(skillFolderOf(path));
4196
4473
  continue;
4474
+ }
4197
4475
  try {
4198
4476
  if (!link) {
4199
- if (recordedSha !== void 0 && hashContent(readFileSync16(abs, "utf8")) === recordedSha)
4477
+ if (recordedSha !== void 0 && hashContent(readFileSync17(abs, "utf8")) === recordedSha) {
4478
+ left.add(skillFolderOf(path));
4200
4479
  continue;
4480
+ }
4201
4481
  accessSync(abs, fsConstants.R_OK);
4202
4482
  }
4203
4483
  } catch {
4484
+ left.add(skillFolderOf(path));
4204
4485
  continue;
4205
4486
  }
4206
- out.set(real, { abs, rel: path, slot, recorded: recordedSha !== void 0, link });
4487
+ const listed = recordedSha !== void 0;
4488
+ out.set(real, {
4489
+ abs,
4490
+ real,
4491
+ rel: path,
4492
+ folder: realOrNull(join24(projectDir, skillFolderOf(path))),
4493
+ slot,
4494
+ recorded: listed,
4495
+ unlisted: !listed && unlistedAt(path) && backupOriginal(path) === null,
4496
+ link
4497
+ });
4207
4498
  }
4208
4499
  }
4209
- return [...out.values()];
4500
+ return { files: [...out.values()], left, ownerOf };
4210
4501
  }
4211
- function settleAside(projectDir, f, restore) {
4502
+ function recordedSlots(projectDir, asset, catalog) {
4503
+ if (catalog?.method.kind !== "skill") return [];
4504
+ const named = catalog.method.skill;
4505
+ if (asset.detail.skill !== named || named !== void 0 && !isSkillName(named)) return [];
4506
+ const out = /* @__PURE__ */ new Set();
4507
+ for (const f of asset.files ?? []) {
4508
+ const parts = f.path.split("/");
4509
+ if (parts.length <= 3) continue;
4510
+ const root = `${parts[0]}/${parts[1]}`;
4511
+ const name = parts[2];
4512
+ if (!SKILL_ROOTS.includes(root) || !isSkillName(name)) continue;
4513
+ if (named !== void 0 && name !== named) continue;
4514
+ const slot = `${root}/${name}`;
4515
+ if (!inSkillFolders(projectDir, slot)) continue;
4516
+ out.add(slot);
4517
+ }
4518
+ return [...out];
4519
+ }
4520
+ function inSkillFolders(projectDir, slot) {
4521
+ if (resolveWriteTarget(join24(projectDir, slot)) === null) return true;
4522
+ const name = slot.split("/").slice(2).join("/");
4523
+ const base = resolveWriteTarget(projectDir) ?? projectDir;
4524
+ const roots = SKILL_ROOTS.flatMap((r) => {
4525
+ const real = resolveWriteTarget(join24(projectDir, r));
4526
+ if (real === null) return [r];
4527
+ const rel = relative5(base, real).split(sep6).join("/");
4528
+ return rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute2(rel) ? [r] : [r, rel];
4529
+ });
4530
+ const places = [...new Set(roots.map((r) => name === "" ? `${r}/` : `${r}/${name}/`))];
4531
+ return inHarnessSlot(projectDir, `${slot}/_`, { places });
4532
+ }
4533
+ function settleAside(projectDir, f, restore, folderRewritten) {
4212
4534
  let now = null;
4213
4535
  let vacant = false;
4214
4536
  try {
4215
- if (f.link) lstatSync4(f.abs);
4216
- else now = readFileSync16(f.abs);
4537
+ if (f.link) lstatSync5(f.abs);
4538
+ else now = readFileSync17(f.abs);
4217
4539
  } catch (e2) {
4218
4540
  const code = e2.code;
4219
4541
  if (code !== "ENOENT" && code !== "ENOTDIR") throw e2;
4220
4542
  vacant = true;
4221
4543
  }
4222
- const keep = vacant ? f.link || !f.recorded : f.link ? lstatSync4(f.abs).isSymbolicLink() && readlinkSync(f.abs) === readlinkSync(f.aside) : now?.equals(readFileSync16(f.aside)) === true;
4544
+ const keep = vacant ? !folderRewritten || f.link || !f.recorded && !f.unlisted : f.link ? lstatSync5(f.abs).isSymbolicLink() && readlinkSync(f.abs) === readlinkSync(f.aside) : now?.equals(readFileSync17(f.aside)) === true;
4223
4545
  if (restore || keep) {
4224
4546
  mkdirSync6(dirname6(f.abs), { recursive: true });
4225
4547
  renameSync2(f.aside, f.abs);
@@ -4227,39 +4549,44 @@ function settleAside(projectDir, f, restore) {
4227
4549
  }
4228
4550
  const backup = f.link ? backupLink(f.abs, f.aside) : backupFromCopy(f.abs, f.aside);
4229
4551
  rmSync3(f.aside, { force: true });
4230
- if (f.recorded || vacant) return { backup, gone: vacant, displaced: null };
4552
+ if (f.recorded || f.unlisted || vacant) return { backup, gone: vacant, displaced: null };
4231
4553
  return {
4232
4554
  backup,
4233
4555
  gone: false,
4234
4556
  displaced: { path: f.rel, change: "displaced", notes: [toProjectRel(projectDir, backup)] }
4235
4557
  };
4236
4558
  }
4237
- function withoutCreated(report, paths) {
4238
- if (paths.size === 0) return report;
4559
+ function settledReport(report, paths, covered) {
4560
+ if (paths.size === 0 && covered.size === 0) return report;
4239
4561
  return {
4240
4562
  ...report,
4241
- attempted: report.attempted.map(
4242
- (r) => r.files === void 0 ? r : {
4563
+ attempted: report.attempted.map((r) => {
4564
+ if (r.files === void 0) return r;
4565
+ const folders = covered.get(r);
4566
+ const created = r.files.created.filter(
4567
+ (x) => !paths.has(x.path) || folders?.has(skillFolderOf(x.path)) === true
4568
+ );
4569
+ return {
4243
4570
  ...r,
4244
- files: { ...r.files, created: r.files.created.filter((x) => !paths.has(x.path)) }
4245
- }
4246
- )
4571
+ files: { ...r.files, created, ...folders ? { covered: [...folders].sort() } : {} }
4572
+ };
4573
+ })
4247
4574
  };
4248
4575
  }
4249
4576
  function pruneEmptyDirs(dir) {
4250
4577
  try {
4251
4578
  for (const e2 of readdirSync3(dir, { withFileTypes: true }))
4252
- if (e2.isDirectory()) pruneEmptyDirs(join22(dir, e2.name));
4579
+ if (e2.isDirectory()) pruneEmptyDirs(join24(dir, e2.name));
4253
4580
  rmdirSync(dir);
4254
4581
  } catch {
4255
4582
  }
4256
4583
  }
4257
4584
  function toProjectRel(projectDir, abs) {
4258
- return relative4(projectDir, abs).split(sep5).join("/");
4585
+ return relative5(projectDir, abs).split(sep6).join("/");
4259
4586
  }
4260
4587
  function recordRefreshedFiles(projectDir, log, report, displaced, writeLog) {
4261
4588
  const next = new Map(
4262
- buildAssetEntries(report, log.scope, log.assets).filter((e2) => e2.files !== void 0).map((e2) => [e2.id, e2.files])
4589
+ buildAssetEntries(report, log.scope, log.assets).filter((e2) => e2.files !== void 0).map((e2) => [e2.id, e2])
4263
4590
  );
4264
4591
  const named = new Map(
4265
4592
  report.attempted.flatMap(
@@ -4268,13 +4595,19 @@ function recordRefreshedFiles(projectDir, log, report, displaced, writeLog) {
4268
4595
  );
4269
4596
  let changed = displaced.length > 0;
4270
4597
  const assets = log.assets.map((a) => {
4271
- const files = next.get(a.id);
4598
+ const entry = next.get(a.id);
4599
+ const files = entry?.files;
4600
+ const partial = entry?.filesPartial === true ? { filesPartial: true } : {};
4601
+ const sameFiles = files === void 0 || JSON.stringify(files) === JSON.stringify(a.files) && entry?.filesPartial === a.filesPartial;
4272
4602
  const { skills } = skillNames(a, a.scope, named.get(a.id));
4273
- const sameFiles = files === void 0 || JSON.stringify(files) === JSON.stringify(a.files);
4274
4603
  const sameSkills = skills === void 0 || JSON.stringify(skills) === JSON.stringify(a.skills);
4275
4604
  if (sameFiles && sameSkills) return a;
4276
4605
  changed = true;
4277
- return { ...a, ...sameFiles ? {} : { files }, ...skills !== void 0 ? { skills } : {} };
4606
+ return {
4607
+ ...a,
4608
+ ...sameFiles ? {} : { files, ...partial },
4609
+ ...skills !== void 0 ? { skills } : {}
4610
+ };
4278
4611
  });
4279
4612
  if (!changed) return log;
4280
4613
  const written = {
@@ -4382,9 +4715,9 @@ function detectVersion(method, spawn, scope, projectDir) {
4382
4715
  case "plugin":
4383
4716
  return detectPluginVersion(method.pluginId, scope, projectDir);
4384
4717
  case "npm": {
4385
- const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join22(projectDir, "node_modules", method.pkg, "package.json");
4718
+ const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join24(projectDir, "node_modules", method.pkg, "package.json");
4386
4719
  if (!pkgJson || !existsSync17(pkgJson)) return void 0;
4387
- const parsed = JSON.parse(readFileSync16(pkgJson, "utf8"));
4720
+ const parsed = JSON.parse(readFileSync17(pkgJson, "utf8"));
4388
4721
  return parsed.version;
4389
4722
  }
4390
4723
  default:
@@ -4396,9 +4729,9 @@ function detectVersion(method, spawn, scope, projectDir) {
4396
4729
  }
4397
4730
  function detectPluginVersion(pluginId, scope, projectDir) {
4398
4731
  const claudeScope = scope === "global" ? "user" : "project";
4399
- const path = join22(homedir2(), ".claude/plugins/installed_plugins.json");
4732
+ const path = join24(homedir2(), ".claude/plugins/installed_plugins.json");
4400
4733
  if (!existsSync17(path)) return void 0;
4401
- const data = JSON.parse(readFileSync16(path, "utf8"));
4734
+ const data = JSON.parse(readFileSync17(path, "utf8"));
4402
4735
  const entries = data.plugins?.[pluginId] ?? [];
4403
4736
  const matches = entries.filter(
4404
4737
  (e2) => claudeScope === "user" ? e2.scope === "user" : e2.scope === "project" && e2.projectPath === projectDir
@@ -4411,7 +4744,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
4411
4744
  }
4412
4745
  function detectGlobalNpmPackageJsonPath(pkg, spawn) {
4413
4746
  const npmRoot = getNpmGlobalRoot(spawn);
4414
- return npmRoot ? join22(npmRoot, pkg, "package.json") : void 0;
4747
+ return npmRoot ? join24(npmRoot, pkg, "package.json") : void 0;
4415
4748
  }
4416
4749
  var npmGlobalRootCache;
4417
4750
  function getNpmGlobalRoot(spawn) {
@@ -4430,8 +4763,8 @@ function getNpmGlobalRoot(spawn) {
4430
4763
 
4431
4764
  // src/key-ids.ts
4432
4765
  init_esm_shims();
4433
- import { readFileSync as readFileSync17 } from "fs";
4434
- import { join as join23 } from "path";
4766
+ import { readFileSync as readFileSync18 } from "fs";
4767
+ import { join as join25 } from "path";
4435
4768
  function renderedKeyIds(harnessRoot, input, clis) {
4436
4769
  const out = /* @__PURE__ */ new Set();
4437
4770
  const add = (path, key) => {
@@ -4454,7 +4787,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
4454
4787
  for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
4455
4788
  for (const key of gitignoreRender().keys()) add(".gitignore", key);
4456
4789
  if (clis.includes("claude")) {
4457
- const template = readFileSync17(join23(harnessRoot, "templates/settings.json"), "utf8");
4790
+ const template = readFileSync18(join25(harnessRoot, "templates/settings.json"), "utf8");
4458
4791
  for (const key of renderSettingsPortion(template, () => true).keys()) {
4459
4792
  add(".claude/settings.json", key);
4460
4793
  }
@@ -4495,7 +4828,7 @@ function withoutAccepts(harnessRoot, spec, previous) {
4495
4828
 
4496
4829
  // src/linked-skill-bodies.ts
4497
4830
  init_esm_shims();
4498
- import { join as join24 } from "path";
4831
+ import { join as join26 } from "path";
4499
4832
  function refreshLinkedSkillBodies(params) {
4500
4833
  const { harnessRoot, projectDir, ids, baseline, journal } = params;
4501
4834
  const bodyKey = (id) => `.agents/skills/${id}/SKILL.md`;
@@ -4506,7 +4839,7 @@ function refreshLinkedSkillBodies(params) {
4506
4839
  );
4507
4840
  const result = writer.result();
4508
4841
  return {
4509
- updated: ours.filter((id) => written.has(join24(projectDir, bodyKey(id)))),
4842
+ updated: ours.filter((id) => written.has(join26(projectDir, bodyKey(id)))),
4510
4843
  notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
4511
4844
  files: result.files,
4512
4845
  backupPaths: result.backupPaths,
@@ -4520,8 +4853,8 @@ function refreshLinkedSkillBodies(params) {
4520
4853
 
4521
4854
  // src/out-of-track.ts
4522
4855
  init_esm_shims();
4523
- import { lstatSync as lstatSync5, readFileSync as readFileSync18, rmSync as rmSync4 } from "fs";
4524
- import { join as join25 } from "path";
4856
+ import { lstatSync as lstatSync6, readFileSync as readFileSync19, rmSync as rmSync4 } from "fs";
4857
+ import { join as join27 } from "path";
4525
4858
  var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
4526
4859
  var ANCHOR_RULE = "uzys-harness";
4527
4860
  function reclaimOutOfTrack(args) {
@@ -4541,16 +4874,17 @@ function reclaimOutOfTrack(args) {
4541
4874
  const name = AGENTS_RULE.exec(f.path)?.[1];
4542
4875
  if (name === void 0 || name === ANCHOR_RULE || rendered.has(name)) continue;
4543
4876
  if (isBaselineExcluded(`.claude/rules/${name}.md`, args.excluded)) continue;
4544
- const abs = join25(projectDir, f.path);
4877
+ const abs = join27(projectDir, f.path);
4545
4878
  if (args.outside.skip(abs)) continue;
4546
- const stat = lstatSync5(abs, { throwIfNoEntry: false });
4879
+ if (!inHarnessSlot(projectDir, f.path)) continue;
4880
+ const stat = lstatSync6(abs, { throwIfNoEntry: false });
4547
4881
  if (stat !== void 0 && !stat.isFile()) continue;
4548
4882
  if (stat !== void 0 && claudeCopy.has(`rules/${name}.md`)) {
4549
4883
  out.kept.push(f.path);
4550
4884
  out.bringBack[f.path] = tracksBringing(name);
4551
4885
  continue;
4552
4886
  }
4553
- const disk = stat === void 0 ? null : readFileSync18(abs, "utf8");
4887
+ const disk = stat === void 0 ? null : readFileSync19(abs, "utf8");
4554
4888
  const verdict = judge({
4555
4889
  op: "remove",
4556
4890
  kind: "harness",
@@ -4582,15 +4916,15 @@ function tracksBringing(rule) {
4582
4916
 
4583
4917
  // src/state.ts
4584
4918
  init_esm_shims();
4585
- import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
4586
- import { join as join26 } from "path";
4919
+ import { existsSync as existsSync18, readFileSync as readFileSync20 } from "fs";
4920
+ import { join as join28 } from "path";
4587
4921
  var META_FILE = ".claude/.installed-tracks";
4588
4922
  var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
4589
4923
  var TRACE_RULES = ["htmx", "nextjs", "data-analysis", "pyside6", "cli-development"].map(
4590
4924
  (r) => `.claude/rules/${r}.md`
4591
4925
  );
4592
4926
  function detectInstallState(projectDir) {
4593
- const hasClaudeDir = existsSync18(join26(projectDir, ".claude"));
4927
+ const hasClaudeDir = existsSync18(join28(projectDir, ".claude"));
4594
4928
  const read = readInstallLogStatus(projectDir);
4595
4929
  if (read.status === "ok") {
4596
4930
  return {
@@ -4610,10 +4944,10 @@ function tracksFromLog(tracks) {
4610
4944
  return [...new Set(tracks.filter(isTrack))].sort();
4611
4945
  }
4612
4946
  function collectTraces(projectDir) {
4613
- const at2 = (rel) => join26(projectDir, rel);
4947
+ const at2 = (rel) => join28(projectDir, rel);
4614
4948
  const text = (rel) => {
4615
4949
  try {
4616
- return readFileSync19(at2(rel), "utf8");
4950
+ return readFileSync20(at2(rel), "utf8");
4617
4951
  } catch {
4618
4952
  return null;
4619
4953
  }
@@ -4677,14 +5011,14 @@ init_esm_shims();
4677
5011
  import {
4678
5012
  copyFileSync,
4679
5013
  existsSync as existsSync19,
4680
- lstatSync as lstatSync6,
5014
+ lstatSync as lstatSync7,
4681
5015
  readdirSync as readdirSync4,
4682
- readFileSync as readFileSync20,
5016
+ readFileSync as readFileSync21,
4683
5017
  rmdirSync as rmdirSync2,
4684
5018
  unlinkSync as unlinkSync2,
4685
5019
  writeFileSync as writeFileSync6
4686
5020
  } from "fs";
4687
- import { basename as basename5, dirname as dirname7, join as join27, relative as relative5, sep as sep6 } from "path";
5021
+ import { basename as basename5, dirname as dirname7, join as join29, relative as relative6, sep as sep7 } from "path";
4688
5022
  var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
4689
5023
  function buildUpdateSpec(projectDir, tracks, only) {
4690
5024
  const log = readInstallLog(projectDir);
@@ -4706,7 +5040,7 @@ function parseUpdateOnly(raw) {
4706
5040
  return { ok: true, groups: [...new Set(raw)] };
4707
5041
  }
4708
5042
  function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
4709
- const claudeDir = join27(projectDir, ".claude");
5043
+ const claudeDir = join29(projectDir, ".claude");
4710
5044
  const startedAt = Date.now();
4711
5045
  const backupsAtStart = new Set(
4712
5046
  collectRunBackups(projectDir, Number.NEGATIVE_INFINITY).map((b2) => b2.backup)
@@ -4777,7 +5111,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
4777
5111
  harnessMcp,
4778
5112
  harnessWrote: harness.wrote
4779
5113
  });
4780
- const settingsPath = join27(claudeDir, "settings.json");
5114
+ const settingsPath = join29(claudeDir, "settings.json");
4781
5115
  if (claudeManaged && wants("hooks") && existsSync19(settingsPath) && !outside.skip(settingsPath)) {
4782
5116
  report.retiredHookRefsRemoved = removeRetiredHookRefs(
4783
5117
  settingsPath,
@@ -4891,6 +5225,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
4891
5225
  const skillRefresh = wants("external") ? (deps.refreshSkills ?? refreshExternalSkills)(projectDir) : { attempted: 0, refreshed: 0, failed: [], notInCatalog: [], unknown: false };
4892
5226
  report.externalSkillsRefreshed = skillRefresh.refreshed;
4893
5227
  report.externalSkillsFailed = skillRefresh.failed;
5228
+ if (skillRefresh.leftAsIs !== void 0) report.externalSkillsLeftAsIs = skillRefresh.leftAsIs;
4894
5229
  const removedOnRecord = wants("external") ? catalogRemovedOnRecord(logAtStart) : [];
4895
5230
  if (removedOnRecord.length > 0) report.catalogRemoved = removedOnRecord;
4896
5231
  const saidAsRemoved = new Set(removedOnRecord.map((e2) => e2.id));
@@ -4924,7 +5259,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
4924
5259
  judged.filter((j4) => j4.recorded === "no-sha" && j4.backup !== void 0).map((j4) => j4.path)
4925
5260
  );
4926
5261
  report.backups = collectRunBackups(projectDir, startedAt, backupsAtStart).map(
4927
- (b2) => existsSync19(join27(projectDir, b2.path)) ? b2 : { ...b2, removed: true }
5262
+ (b2) => existsSync19(join29(projectDir, b2.path)) ? b2 : { ...b2, removed: true }
4928
5263
  );
4929
5264
  const noChecksum = report.backups.map((b2) => b2.path).filter((p2) => unmeasured.has(p2));
4930
5265
  if (noChecksum.length > 0) report.noChecksum = noChecksum;
@@ -5007,7 +5342,7 @@ function reclaimOutOfTrackRecorded(projectDir, outside) {
5007
5342
  function deadMcpLeft(projectDir) {
5008
5343
  let servers;
5009
5344
  try {
5010
- servers = JSON.parse(readFileSync20(join27(projectDir, ".mcp.json"), "utf8")).mcpServers;
5345
+ servers = JSON.parse(readFileSync21(join29(projectDir, ".mcp.json"), "utf8")).mcpServers;
5011
5346
  } catch {
5012
5347
  return [];
5013
5348
  }
@@ -5022,7 +5357,7 @@ function retiredFeaturesPart(projectDir, f) {
5022
5357
  }
5023
5358
  function legacyHookOnDisk(projectDir, rel) {
5024
5359
  try {
5025
- return hasLegacyHarnessHook(readFileSync20(join27(projectDir, rel), "utf8"));
5360
+ return hasLegacyHarnessHook(readFileSync21(join29(projectDir, rel), "utf8"));
5026
5361
  } catch {
5027
5362
  return false;
5028
5363
  }
@@ -5048,13 +5383,13 @@ function writeUpdateSharedFiles(args) {
5048
5383
  excluded: adapterExcluded(harnessRoot, excluded)
5049
5384
  });
5050
5385
  const tracks = installedTracks(projectDir);
5051
- const settingsSource = join27(templatesDir, "settings.json");
5386
+ const settingsSource = join29(templatesDir, "settings.json");
5052
5387
  if (args.claudeManaged && wants("hooks") && existsSync19(settingsSource)) {
5053
5388
  const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
5054
5389
  const manifest = buildManifest(spec);
5055
5390
  writeSettingsShared(
5056
5391
  writer,
5057
- readFileSync20(settingsSource, "utf8"),
5392
+ readFileSync21(settingsSource, "utf8"),
5058
5393
  projectDir,
5059
5394
  log,
5060
5395
  (script) => {
@@ -5123,8 +5458,8 @@ function catalogRemovedOnRecord(log) {
5123
5458
  }
5124
5459
  function staleSkillDirs(projectDir, claudeDir, clis) {
5125
5460
  const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
5126
- const dirs = [join27(claudeDir, "skills")];
5127
- if (clis.some((cli) => cli !== "claude")) dirs.push(join27(projectDir, ".agents", "skills"));
5461
+ const dirs = [join29(claudeDir, "skills")];
5462
+ if (clis.some((cli) => cli !== "claude")) dirs.push(join29(projectDir, ".agents", "skills"));
5128
5463
  const ids = /* @__PURE__ */ new Set();
5129
5464
  for (const dir of dirs) {
5130
5465
  if (!existsSync19(dir)) continue;
@@ -5135,13 +5470,13 @@ function staleSkillDirs(projectDir, claudeDir, clis) {
5135
5470
  return [...ids];
5136
5471
  }
5137
5472
  function staleAgentFiles(claudeDir) {
5138
- const dir = join27(claudeDir, "agents");
5473
+ const dir = join29(claudeDir, "agents");
5139
5474
  if (!existsSync19(dir)) return [];
5140
5475
  return readdirSync4(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename5(e2.name, ".md")).filter((id) => RETIRED_AGENT_IDS.includes(id));
5141
5476
  }
5142
5477
  function demotedAgentFiles(claudeDir, tracks) {
5143
5478
  if (tracks.length === 0) return [];
5144
- const dir = join27(claudeDir, "agents");
5479
+ const dir = join29(claudeDir, "agents");
5145
5480
  if (!existsSync19(dir)) return [];
5146
5481
  const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
5147
5482
  ([id]) => id
@@ -5157,19 +5492,19 @@ function updateHarnessTargets(projectDir, templatesDir, log, wants, report, fore
5157
5492
  if (!wants(e2.target.startsWith(".claude/hooks/") ? "hooks" : "rules")) continue;
5158
5493
  if (e2.target.startsWith(".claude/") && !claude) continue;
5159
5494
  if (isBaselineExcluded(e2.target, excluded)) continue;
5160
- const source = join27(templatesDir, e2.source);
5495
+ const source = join29(templatesDir, e2.source);
5161
5496
  if (!existsSync19(source)) continue;
5162
5497
  const policy = POLICY_DIRS.find((d2) => e2.target.startsWith(`.claude/${d2.dir}/`));
5163
5498
  files.push({
5164
5499
  path: e2.target,
5165
5500
  source,
5166
5501
  unit: e2.target,
5167
- unitExisted: existsSync19(join27(projectDir, e2.target)),
5502
+ unitExisted: existsSync19(join29(projectDir, e2.target)),
5168
5503
  label: policy === void 0 ? e2.target : `.claude/${policy.dir}`
5169
5504
  });
5170
5505
  }
5171
5506
  const refreshedSkills = [];
5172
- const skillsSource = join27(templatesDir, "skills");
5507
+ const skillsSource = join29(templatesDir, "skills");
5173
5508
  if (!claude || !(wants("skills") || wants("new-skills")) || !existsSync19(skillsSource)) {
5174
5509
  return { files, refreshedSkills };
5175
5510
  }
@@ -5179,20 +5514,20 @@ function updateHarnessTargets(projectDir, templatesDir, log, wants, report, fore
5179
5514
  for (const d2 of readdirSync4(skillsSource, { withFileTypes: true })) {
5180
5515
  if (!d2.isDirectory()) continue;
5181
5516
  const unit = `.claude/skills/${d2.name}`;
5182
- if (occupiedByNonDirectory(join27(projectDir, unit))) {
5517
+ if (occupiedByNonDirectory(join29(projectDir, unit))) {
5183
5518
  report.skillsSkippedLinks.push(d2.name);
5184
5519
  continue;
5185
5520
  }
5186
5521
  if (!ids.has(d2.name) || excludedSkill(d2.name)) continue;
5187
- const existed = existsSync19(join27(projectDir, unit));
5522
+ const existed = existsSync19(join29(projectDir, unit));
5188
5523
  if (existed ? !wants("skills") : !wants("new-skills") || !creatable.has(d2.name)) continue;
5189
- for (const rel of listFilesRecursive(join27(skillsSource, d2.name))) {
5524
+ for (const rel of listFilesRecursive(join29(skillsSource, d2.name))) {
5190
5525
  const foreign = foreignOwnedTarget(projectDir, `${unit}/${rel}`);
5191
5526
  if (foreign !== null) {
5192
5527
  if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
5193
5528
  continue;
5194
5529
  }
5195
- const source = join27(skillsSource, d2.name, rel);
5530
+ const source = join29(skillsSource, d2.name, rel);
5196
5531
  files.push({
5197
5532
  path: `${unit}/${rel}`,
5198
5533
  source,
@@ -5230,8 +5565,8 @@ function updateHarnessFiles(args) {
5230
5565
  report.updated[label] = 0;
5231
5566
  const ctx = { prefix: dir, outside, recordOf, projectDir, judged };
5232
5567
  report.pruned[label] = pruneOrphans(
5233
- join27(projectDir, label),
5234
- join27(templatesDir, dir),
5568
+ join29(projectDir, label),
5569
+ join29(templatesDir, dir),
5235
5570
  ext,
5236
5571
  ctx,
5237
5572
  now
@@ -5299,7 +5634,7 @@ function updateHarnessFiles(args) {
5299
5634
  function removedRow(projectDir, path, why, judged, backupAbs) {
5300
5635
  const { line, recorded: recorded2 } = judged;
5301
5636
  if (backupAbs === null) return { path, verdict: "remove", recorded: recorded2, line: `${why} \u2014 ${line}` };
5302
- const backup = relative5(projectDir, backupAbs).split(sep6).join("/");
5637
+ const backup = relative6(projectDir, backupAbs).split(sep7).join("/");
5303
5638
  return {
5304
5639
  path,
5305
5640
  verdict: "backup+remove",
@@ -5315,19 +5650,19 @@ function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now,
5315
5650
  const backedUp = [];
5316
5651
  const failed = [];
5317
5652
  for (const id of ids) {
5318
- const targetSkill = join27(projectDir, ".claude/skills", id);
5319
- const bundled = new Set(listFilesRecursive(join27(templatesDir, "skills", id)));
5653
+ const targetSkill = join29(projectDir, ".claude/skills", id);
5654
+ const bundled = new Set(listFilesRecursive(join29(templatesDir, "skills", id)));
5320
5655
  for (const rel of listFilesRecursive(targetSkill)) {
5321
5656
  if (bundled.has(rel) || basename5(rel).includes(".backup-")) continue;
5322
5657
  if (foreignOwnedTarget(projectDir, `.claude/skills/${id}/${rel}`) !== null) continue;
5323
- const targetFile = join27(targetSkill, rel);
5658
+ const targetFile = join29(targetSkill, rel);
5324
5659
  if (outside.skip(targetFile)) continue;
5325
5660
  const path = `.claude/skills/${id}/${rel}`;
5326
5661
  const rec = recordOf(path);
5327
5662
  let line;
5328
5663
  let backupAbs = null;
5329
5664
  try {
5330
- const disk = rec.state === "sha" ? readFileSync20(targetFile, "utf8") : "";
5665
+ const disk = rec.state === "sha" ? readFileSync21(targetFile, "utf8") : "";
5331
5666
  line = rec.state === "none" ? NOT_RECORDED_REMOVED : judge({ op: "remove", kind: "harness", rec, disk, next: null }).line;
5332
5667
  if (rec.state !== "sha" || hashContent(disk) !== rec.sha256) {
5333
5668
  backupAbs = backupFile(targetFile, now);
@@ -5395,20 +5730,20 @@ function collectRunBackups(projectDir, startedAt, exclude) {
5395
5730
  const consider = (rel) => {
5396
5731
  if (!BACKUP_SUFFIX.test(rel)) return;
5397
5732
  if (exclude?.has(rel)) return;
5398
- const abs = join27(projectDir, rel);
5399
- if (lstatSync6(abs).mtimeMs < startedAt - 1e3) return;
5733
+ const abs = join29(projectDir, rel);
5734
+ if (lstatSync7(abs).mtimeMs < startedAt - 1e3) return;
5400
5735
  out.push({ path: rel.replace(BACKUP_SUFFIX, ""), backup: rel });
5401
5736
  };
5402
5737
  for (const e2 of withoutAppleDouble(readdirSync4(projectDir, { withFileTypes: true })))
5403
5738
  if (e2.isFile()) consider(e2.name);
5404
5739
  for (const dir of BACKUP_SCAN_DIRS) {
5405
- for (const { rel } of listEntriesRecursive(join27(projectDir, dir))) consider(`${dir}/${rel}`);
5740
+ for (const { rel } of listEntriesRecursive(join29(projectDir, dir))) consider(`${dir}/${rel}`);
5406
5741
  }
5407
5742
  return out.sort((a, b2) => a.path.localeCompare(b2.path));
5408
5743
  }
5409
5744
  function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new Set()) {
5410
- const dir = join27(projectDir, ".uzys-agent-harness");
5411
- const file = join27(dir, "update-backups.json");
5745
+ const dir = join29(projectDir, ".uzys-agent-harness");
5746
+ const file = join29(dir, "update-backups.json");
5412
5747
  try {
5413
5748
  if (backups.length === 0) {
5414
5749
  if (existsSync19(file)) unlinkSync2(file);
@@ -5440,25 +5775,25 @@ function trackOnlyFileAssets(tracks) {
5440
5775
  );
5441
5776
  }
5442
5777
  function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, now = /* @__PURE__ */ new Date()) {
5443
- if (existsSync19(join27(projectDir, LEGACY_ANCHOR_FILE2))) {
5778
+ if (existsSync19(join29(projectDir, LEGACY_ANCHOR_FILE2))) {
5444
5779
  report.legacyAnchor = LEGACY_ANCHOR_FILE2;
5445
5780
  }
5446
- const templateMd = join27(templatesDir, "CLAUDE.md");
5781
+ const templateMd = join29(templatesDir, "CLAUDE.md");
5447
5782
  if (!existsSync19(templateMd)) return;
5448
- const anchor = join27(projectDir, HARNESS_ANCHOR_FILE);
5783
+ const anchor = join29(projectDir, HARNESS_ANCHOR_FILE);
5449
5784
  if (outside.skip(anchor)) return;
5450
5785
  const existed = existsSync19(anchor);
5451
5786
  if (existed) {
5452
- const current = readFileSync20(anchor, "utf8");
5453
- if (current !== readFileSync20(templateMd, "utf8")) {
5787
+ const current = readFileSync21(anchor, "utf8");
5788
+ if (current !== readFileSync21(templateMd, "utf8")) {
5454
5789
  const recorded2 = readInstallLog(projectDir)?.templates.rootClaudeMd;
5455
5790
  const owned = recorded2?.path === HARNESS_ANCHOR_FILE && hashContent(current) === recorded2.sha256;
5456
5791
  if (!owned) {
5457
5792
  const backup = backupFile(anchor, now);
5458
5793
  report.anchorBackedUp = true;
5459
- const rel = relative5(projectDir, backup).split(sep6).join("/");
5794
+ const rel = relative6(projectDir, backup).split(sep7).join("/");
5460
5795
  const rec = recorded2?.path === HARNESS_ANCHOR_FILE ? { state: "sha", sha256: recorded2.sha256 } : { state: "no-sha" };
5461
- const next = readFileSync20(templateMd, "utf8");
5796
+ const next = readFileSync21(templateMd, "utf8");
5462
5797
  const { line } = judge({ op: "write", kind: "harness", rec, disk: current, next });
5463
5798
  judged.push({
5464
5799
  path: HARNESS_ANCHOR_FILE,
@@ -5483,15 +5818,15 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, no
5483
5818
  recordAnchorBaseline(projectDir, anchor);
5484
5819
  }
5485
5820
  function upsertRootImport(projectDir, outside) {
5486
- const target = join27(projectDir, "CLAUDE.md");
5821
+ const target = join29(projectDir, "CLAUDE.md");
5487
5822
  if (outside.skip(target)) return false;
5488
- const existing = existsSync19(target) ? readFileSync20(target, "utf8") : null;
5823
+ const existing = existsSync19(target) ? readFileSync21(target, "utf8") : null;
5489
5824
  const next = upsertHarnessImport(existing, {
5490
5825
  projectName: basename5(projectDir),
5491
5826
  // ADR-085 — update 는 선택 목록을 인자로 받지 않는다. 앞 단계(하네스 파일 쓰기)가 지나간 디스크로 판정한다:
5492
5827
  // `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
5493
5828
  continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
5494
- (id) => existsSync19(join27(projectDir, ".claude/skills", id))
5829
+ (id) => existsSync19(join29(projectDir, ".claude/skills", id))
5495
5830
  )
5496
5831
  });
5497
5832
  if (next === existing) return false;
@@ -5512,7 +5847,7 @@ function recordAnchorBaseline(projectDir, anchor) {
5512
5847
  ...log.templates,
5513
5848
  rootClaudeMd: {
5514
5849
  path: HARNESS_ANCHOR_FILE,
5515
- sha256: hashContent(readFileSync20(anchor, "utf8"))
5850
+ sha256: hashContent(readFileSync21(anchor, "utf8"))
5516
5851
  }
5517
5852
  }
5518
5853
  };
@@ -5526,7 +5861,7 @@ function excludedBaselineOnDisk(projectDir, log) {
5526
5861
  if (excluded.size === 0) return [];
5527
5862
  const spec = buildAssetSpec({ tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS });
5528
5863
  return buildManifest(spec).filter(
5529
- (e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync19(join27(projectDir, e2.target))
5864
+ (e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync19(join29(projectDir, e2.target))
5530
5865
  ).map((e2) => e2.target);
5531
5866
  }
5532
5867
  function excludedSkillOf(log) {
@@ -5554,7 +5889,7 @@ function recordedRenderInputs(projectDir, log, tracks = installedTracks(projectD
5554
5889
  function recordedBundledSkills(projectDir, log) {
5555
5890
  if (log === null) {
5556
5891
  return INTERNAL_BUNDLED_SKILL_IDS.filter(
5557
- (id) => existsSync19(join27(projectDir, ".claude/skills", id)) || existsSync19(join27(projectDir, ".agents/skills", id))
5892
+ (id) => existsSync19(join29(projectDir, ".claude/skills", id)) || existsSync19(join29(projectDir, ".agents/skills", id))
5558
5893
  );
5559
5894
  }
5560
5895
  const ids = recordedSkillIds(projectDir, log);
@@ -5605,9 +5940,9 @@ var SHARED_SKILLS_DIR = ".agents/skills";
5605
5940
  var SHARED_SKILL_FILE = /^\.agents\/skills\/([^/]+)\//;
5606
5941
  var SHARED_SKILL_MD = /^\.agents\/skills\/([^/]+)\/SKILL\.md$/;
5607
5942
  function sharedSkillIdsOnDisk(projectDir) {
5608
- const root = join27(projectDir, SHARED_SKILLS_DIR);
5943
+ const root = join29(projectDir, SHARED_SKILLS_DIR);
5609
5944
  if (!existsSync19(root)) return /* @__PURE__ */ new Set();
5610
- return new Set(readdirSync4(root).filter((id) => existsSync19(join27(root, id, "SKILL.md"))));
5945
+ return new Set(readdirSync4(root).filter((id) => existsSync19(join29(root, id, "SKILL.md"))));
5611
5946
  }
5612
5947
  var CREATED_SHARED_HOW = {
5613
5948
  "opencode.json": "created from template (harness part + template defaults)",
@@ -5634,14 +5969,14 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp, li
5634
5969
  const log = readInstallLog(projectDir);
5635
5970
  const presentBefore = sharedSkillIdsOnDisk(projectDir);
5636
5971
  const rulesBefore = /* @__PURE__ */ new Set();
5637
- const rulesDir = join27(projectDir, ".agents", "rules");
5972
+ const rulesDir = join29(projectDir, ".agents", "rules");
5638
5973
  if (existsSync19(rulesDir)) {
5639
5974
  try {
5640
5975
  for (const f of readdirSync4(rulesDir)) rulesBefore.add(f);
5641
5976
  } catch {
5642
5977
  }
5643
5978
  }
5644
- const agentsMdBefore = existsSync19(join27(projectDir, "AGENTS.md"));
5979
+ const agentsMdBefore = existsSync19(join29(projectDir, "AGENTS.md"));
5645
5980
  const result = runCliTransforms({
5646
5981
  harnessRoot,
5647
5982
  projectDir,
@@ -5780,7 +6115,7 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp, li
5780
6115
  ],
5781
6116
  anchorRetired: result.anchorRetired,
5782
6117
  retiredCommands: (result.opencode?.retiredCommands ?? []).map(
5783
- (p2) => relative5(projectDir, p2).split(sep6).join("/")
6118
+ (p2) => relative6(projectDir, p2).split(sep7).join("/")
5784
6119
  ),
5785
6120
  anchorDisplaced,
5786
6121
  agentsMd: agentsMdNews(result.agentsMd, agentsMdBefore, priorPaths),
@@ -5818,14 +6153,15 @@ function pruneOrphans(target, source, ext, ctx, now) {
5818
6153
  if (!existsSync19(target) || !existsSync19(source)) return [];
5819
6154
  const removed = [];
5820
6155
  for (const file of readdirSync4(target)) {
5821
- if (!file.endsWith(ext) || existsSync19(join27(source, file))) continue;
5822
- const targetFile = join27(target, file);
6156
+ if (!file.endsWith(ext) || existsSync19(join29(source, file))) continue;
6157
+ const targetFile = join29(target, file);
5823
6158
  const path = `.claude/${ctx.prefix}/${file}`;
5824
6159
  const rec = ctx.recordOf(path);
5825
6160
  try {
5826
- const disk = readFileSync20(targetFile, "utf8");
6161
+ const disk = readFileSync21(targetFile, "utf8");
5827
6162
  const { verdict, line } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
5828
6163
  if (verdict === "leave" || ctx.outside.skip(targetFile)) continue;
6164
+ if (!inHarnessSlot(ctx.projectDir, path, HARNESS_FILES, { follow: true })) continue;
5829
6165
  const backupAbs = verdict === "backup+remove" ? backupFile(targetFile, now) : null;
5830
6166
  unlinkSync2(targetFile);
5831
6167
  removed.push(file);
@@ -5844,7 +6180,7 @@ function pruneOrphans(target, source, ext, ctx, now) {
5844
6180
  return removed;
5845
6181
  }
5846
6182
  function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
5847
- const target = join27(projectDir, ".mcp-allowlist");
6183
+ const target = join29(projectDir, ".mcp-allowlist");
5848
6184
  if (!existsSync19(target)) return null;
5849
6185
  try {
5850
6186
  const backup = backupFile(target, now);
@@ -5866,11 +6202,11 @@ function recordClaudeCommand(log, tracks) {
5866
6202
  function runInstall(ctx) {
5867
6203
  const { harnessRoot, projectDir } = ctx;
5868
6204
  const mode = ctx.mode ?? "fresh";
5869
- const templatesDir = join28(harnessRoot, "templates");
6205
+ const templatesDir = join30(harnessRoot, "templates");
5870
6206
  if (!existsSync20(templatesDir)) {
5871
6207
  throw new Error(`Templates dir not found: ${templatesDir}`);
5872
6208
  }
5873
- const claudeDir = join28(projectDir, ".claude");
6209
+ const claudeDir = join30(projectDir, ".claude");
5874
6210
  const previousLog = readInstallLog(projectDir);
5875
6211
  const legacyAnchor = mode === "update" ? null : legacyAnchorTrace(projectDir, ctx.spec);
5876
6212
  if (mode === "update" && previousLog === null) {
@@ -5895,13 +6231,13 @@ function runInstall(ctx) {
5895
6231
  const manifestSpec = buildManifestSpec(spec);
5896
6232
  const baselineExcluded = new Set(spec.baselineExclude ?? []);
5897
6233
  if (spec.cli.includes("claude")) {
5898
- const settingsPath = join28(projectDir, ".claude", "settings.json");
6234
+ const settingsPath = join30(projectDir, ".claude", "settings.json");
5899
6235
  if (existsSync20(settingsPath)) {
5900
6236
  try {
5901
- JSON.parse(readFileSync21(settingsPath, "utf8"));
6237
+ JSON.parse(readFileSync22(settingsPath, "utf8"));
5902
6238
  } catch {
5903
6239
  throw new Error(
5904
- `${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`
6240
+ `${relative7(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`
5905
6241
  );
5906
6242
  }
5907
6243
  }
@@ -6195,7 +6531,7 @@ function releasedThisRun(args) {
6195
6531
  }
6196
6532
  if (id.startsWith(BASELINE_PREFIX)) {
6197
6533
  const again2 = manifest.some(
6198
- (e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync20(join28(projectDir, e2.target))
6534
+ (e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync20(join30(projectDir, e2.target))
6199
6535
  );
6200
6536
  return { id, again: again2, tail: "the harness manages it again" };
6201
6537
  }
@@ -6204,7 +6540,7 @@ function releasedThisRun(args) {
6204
6540
  options: spec.options,
6205
6541
  ...spec.userOverride ? { userOverride: spec.userOverride } : {}
6206
6542
  });
6207
- const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync20(join28(projectDir, d2, id)));
6543
+ const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync20(join30(projectDir, d2, id)));
6208
6544
  return {
6209
6545
  id,
6210
6546
  again,
@@ -6216,10 +6552,10 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
6216
6552
  const done = new Set(touched);
6217
6553
  return keyExclude.flatMap((id) => {
6218
6554
  const path = sharedPathOfKeyId(id);
6219
- if (path === null || done.has(path) || !existsSync20(join28(projectDir, path))) return [];
6555
+ if (path === null || done.has(path) || !existsSync20(join30(projectDir, path))) return [];
6220
6556
  const name = adapterFor(path);
6221
6557
  const keys = [...excludedKeys(path, [id])];
6222
- const present = name === null ? null : ADAPTERS[name].read(readFileSync21(join28(projectDir, path), "utf8"), keys, projectDir);
6558
+ const present = name === null ? null : ADAPTERS[name].read(readFileSync22(join30(projectDir, path), "utf8"), keys, projectDir);
6223
6559
  return present !== null && present.size === 0 ? [] : [{ id, path }];
6224
6560
  });
6225
6561
  }
@@ -6263,7 +6599,7 @@ function installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, w
6263
6599
  continue;
6264
6600
  }
6265
6601
  if (!isCliNeutralTarget(entry.target)) continue;
6266
- const source = join28(templatesDir, entry.source);
6602
+ const source = join30(templatesDir, entry.source);
6267
6603
  if (!existsSync20(source)) {
6268
6604
  result.skipped += 1;
6269
6605
  continue;
@@ -6284,13 +6620,13 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6284
6620
  if (!entry.applies(manifestSpec)) {
6285
6621
  continue;
6286
6622
  }
6287
- const target = join28(projectDir, entry.target);
6623
+ const target = join30(projectDir, entry.target);
6288
6624
  if (isBaselineExcluded(entry.target, baselineExcluded)) {
6289
6625
  result.excluded.push(entry.target);
6290
6626
  if (existsSync20(target)) result.excludedOnDisk.push(entry.target);
6291
6627
  continue;
6292
6628
  }
6293
- const source = join28(templatesDir, entry.source);
6629
+ const source = join30(templatesDir, entry.source);
6294
6630
  if (!existsSync20(source)) {
6295
6631
  result.skipped += 1;
6296
6632
  continue;
@@ -6321,18 +6657,18 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6321
6657
  if (!result.foreignOwned.includes(slot)) result.foreignOwned.push(slot);
6322
6658
  continue;
6323
6659
  }
6324
- if (writer.skipOutside(join28(projectDir, entry.target, rel))) {
6660
+ if (writer.skipOutside(join30(projectDir, entry.target, rel))) {
6325
6661
  leftOutside += 1;
6326
6662
  continue;
6327
6663
  }
6328
- writer.harness(`${entry.target}/${rel}`, { source: join28(source, rel) });
6664
+ writer.harness(`${entry.target}/${rel}`, { source: join30(source, rel) });
6329
6665
  }
6330
6666
  if (leftOutside > 0) continue;
6331
6667
  result.dirsCopied += 1;
6332
6668
  }
6333
6669
  accumulateCategory(result.categories, entry);
6334
6670
  }
6335
- const hookDir = join28(projectDir, ".claude/hooks");
6671
+ const hookDir = join30(projectDir, ".claude/hooks");
6336
6672
  if (existsSync20(hookDir)) {
6337
6673
  chmodHooksSync(hookDir, projectDir);
6338
6674
  }
@@ -6354,8 +6690,8 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6354
6690
  });
6355
6691
  result.keptHookRefs.push(
6356
6692
  ...findStaleHookRefs(
6357
- join28(projectDir, ".claude", "settings.json"),
6358
- join28(projectDir, ".claude")
6693
+ join30(projectDir, ".claude", "settings.json"),
6694
+ join30(projectDir, ".claude")
6359
6695
  )
6360
6696
  );
6361
6697
  }
@@ -6364,7 +6700,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6364
6700
  // ADR-085 · update(`upsertRootImport`)와 같은 판정 — 실제로 깔린 스킬만 안내한다. 밖 링크라 쓰지 않은
6365
6701
  // 스킬(ADR-098)을 "installed" 라 적으면 다음 update 가 그 절을 걷어 내 두 동작이 서로 반대로 쓴다.
6366
6702
  (manifestSpec.selectedInternalSkills ?? []).filter(
6367
- (id) => existsSync20(join28(projectDir, ".claude", "skills", id))
6703
+ (id) => existsSync20(join30(projectDir, ".claude", "skills", id))
6368
6704
  ),
6369
6705
  harnessRoot,
6370
6706
  writer
@@ -6380,7 +6716,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6380
6716
  function writeSettingsPortion(writer, source, projectDir, previousLog, hookInstalled) {
6381
6717
  return writeSettingsShared(
6382
6718
  writer,
6383
- readFileSync21(source, "utf8"),
6719
+ readFileSync22(source, "utf8"),
6384
6720
  projectDir,
6385
6721
  previousLog,
6386
6722
  hookInstalled
@@ -6430,9 +6766,13 @@ function runExternalPhase(ctx, previousLog) {
6430
6766
  ctx.onProgress?.({ type: "external-start", assetCount: targets.length });
6431
6767
  const skills = targets.filter((a) => a.method.kind === "skill");
6432
6768
  const skillIds = new Set(skills.map((a) => a.id));
6769
+ const slotCtx = { log: previousLog, scope: spec.scope };
6433
6770
  const settle = holdSkillSlots(projectDir, {
6434
6771
  recorded: (previousLog?.assets ?? []).filter((a) => a.method === "skill" && skillIds.has(a.id)),
6435
- slots: skillSlotsFor(skills, spec.cli, { log: previousLog, scope: spec.scope }),
6772
+ assets: skills,
6773
+ slots: skillSlotsFor(skills, spec.cli, slotCtx),
6774
+ // #902 — 옛 판이 파일 목록 없이 깐 자산의 자리 — 그 안 파일은 되살리지 않고 백업만 남긴다
6775
+ unlisted: unlistedSkillSlots(skills, spec.cli, slotCtx),
6436
6776
  log: previousLog,
6437
6777
  clis: spec.cli,
6438
6778
  scope: spec.scope
@@ -6451,7 +6791,7 @@ function runExternalPhase(ctx, previousLog) {
6451
6791
  throw e2;
6452
6792
  }
6453
6793
  const { report: external, backups, displaced } = settle(ran);
6454
- const displacedBackups = new Set(displaced.map((f) => join28(projectDir, f.notes[0] ?? "")));
6794
+ const displacedBackups = new Set(displaced.map((f) => join30(projectDir, f.notes[0] ?? "")));
6455
6795
  const editBackups = backups.filter((b2) => !displacedBackups.has(b2));
6456
6796
  ctx.onProgress?.({
6457
6797
  type: "external-complete",
@@ -6524,7 +6864,7 @@ function recordInterruptedInstall(ctx, error, run) {
6524
6864
  const codexTrust = registeredTrust(journal.codexOptIn, ctx.projectDir);
6525
6865
  const written = writtenSoFar(ledger, cliFiles, rootFiles, stage.rootImportWritten);
6526
6866
  if (codexTrust) written.push(`${codexTrust.configPath} (Codex trust entry for this folder)`);
6527
- const projectRel = (abs) => relative6(ctx.projectDir, abs).split(sep7).join("/");
6867
+ const projectRel = (abs) => relative7(ctx.projectDir, abs).split(sep8).join("/");
6528
6868
  const writtenSet = new Set(written);
6529
6869
  const backups = [...own.backups, ...journal.backups].map((abs) => {
6530
6870
  const original = projectRel(backupOriginal(abs) ?? abs);
@@ -6664,9 +7004,9 @@ function accumulateCategory(cats, entry) {
6664
7004
  }
6665
7005
  }
6666
7006
  function writeRootClaudeMd(projectDir, continuousSkills, harnessRoot, writer) {
6667
- const target = join28(projectDir, "CLAUDE.md");
7007
+ const target = join30(projectDir, "CLAUDE.md");
6668
7008
  if (writer.skipOutside(target)) return null;
6669
- const existing = existsSync20(target) ? readFileSync21(target, "utf-8") : null;
7009
+ const existing = existsSync20(target) ? readFileSync22(target, "utf-8") : null;
6670
7010
  const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
6671
7011
  const content = upsertHarnessImport(existing, {
6672
7012
  projectName: basename6(projectDir),
@@ -6698,8 +7038,8 @@ function listHookFiles(hookDir) {
6698
7038
 
6699
7039
  // src/commands/install-render.ts
6700
7040
  init_esm_shims();
6701
- import { existsSync as existsSync22, readdirSync as readdirSync6, readFileSync as readFileSync23 } from "fs";
6702
- import { join as join30, relative as relative7 } from "path";
7041
+ import { existsSync as existsSync22, readdirSync as readdirSync6, readFileSync as readFileSync24 } from "fs";
7042
+ import { join as join32, relative as relative8 } from "path";
6703
7043
 
6704
7044
  // src/preset-recommend.ts
6705
7045
  init_esm_shims();
@@ -6721,16 +7061,21 @@ function trackDefaultAssets(tracks) {
6721
7061
  ])
6722
7062
  ];
6723
7063
  }
6724
- function finalSelectedAssets(tracks, userOverride) {
7064
+ function finalSelectedAssets(tracks, userOverride, keyExclude = []) {
6725
7065
  const selected = new Set(trackDefaultAssets(tracks));
6726
7066
  if (userOverride) {
6727
7067
  for (const id of userOverride.forceExclude) selected.delete(id);
6728
7068
  for (const id of userOverride.forceInclude) selected.add(id);
6729
7069
  }
7070
+ if (keyExclude.length > 0) {
7071
+ for (const id of droppedMcpAssetIds(resolveBundleRoot(), new Set(keyExclude))) {
7072
+ selected.delete(id);
7073
+ }
7074
+ }
6730
7075
  return [...selected].sort();
6731
7076
  }
6732
7077
  function residentSelectedAssets(spec) {
6733
- return finalSelectedAssets(spec.tracks, spec.userOverride).filter((id) => {
7078
+ return finalSelectedAssets(spec.tracks, spec.userOverride, spec.keyExclude).filter((id) => {
6734
7079
  const asset = EXTERNAL_ASSETS.find((a) => a.id === id);
6735
7080
  return asset === void 0 || assetReachesCli(asset, spec.cli);
6736
7081
  });
@@ -6749,10 +7094,10 @@ function groupAssetsByCategory(assetIds) {
6749
7094
 
6750
7095
  // src/resident-entries.ts
6751
7096
  init_esm_shims();
6752
- import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
6753
- import { basename as basename7, join as join29 } from "path";
7097
+ import { existsSync as existsSync21, readFileSync as readFileSync23 } from "fs";
7098
+ import { basename as basename7, join as join31 } from "path";
6754
7099
  function residentEntries(spec) {
6755
- const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join29(spec.projectDir, d2, id)));
7100
+ const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join31(spec.projectDir, d2, id)));
6756
7101
  const forceExclude = (spec.userOverride?.forceExclude ?? []).filter((id) => !skillThere(id));
6757
7102
  const assetSpec = buildManifestSpec(
6758
7103
  spec.userOverride ? { ...spec, userOverride: { ...spec.userOverride, forceExclude } } : spec
@@ -6778,7 +7123,7 @@ function residentEntries(spec) {
6778
7123
  function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
6779
7124
  if (contextFiles !== void 0) return residentCost(entries, void 0, spec.cli, contextFiles);
6780
7125
  const cost = residentCost(entries, void 0, spec.cli, plannedClaudeContext(spec));
6781
- if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync21(join29(spec.projectDir, "AGENTS.md"))) {
7126
+ if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync21(join31(spec.projectDir, "AGENTS.md"))) {
6782
7127
  return makeResidentCost({
6783
7128
  ...cost,
6784
7129
  contextNote: "planned, not counting text already in AGENTS.md"
@@ -6789,8 +7134,8 @@ function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
6789
7134
  function plannedClaudeContext(spec) {
6790
7135
  if (!spec.cli.includes("claude")) return void 0;
6791
7136
  const root = resolveBundleRoot();
6792
- const rootClaude = join29(spec.projectDir, "CLAUDE.md");
6793
- const existing = existsSync21(rootClaude) ? readFileSync22(rootClaude, "utf-8") : null;
7137
+ const rootClaude = join31(spec.projectDir, "CLAUDE.md");
7138
+ const existing = existsSync21(rootClaude) ? readFileSync23(rootClaude, "utf-8") : null;
6794
7139
  const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
6795
7140
  if (existing === null && seeded === null) return void 0;
6796
7141
  const manifestSpec = buildManifestSpec(spec);
@@ -6799,14 +7144,14 @@ function plannedClaudeContext(spec) {
6799
7144
  continuousSkills: manifestSpec.selectedInternalSkills ?? [],
6800
7145
  ...seeded === null ? {} : { projectContext: seeded }
6801
7146
  });
6802
- return [join29(root, "templates", "CLAUDE.md"), { text }];
7147
+ return [join31(root, "templates", "CLAUDE.md"), { text }];
6803
7148
  }
6804
7149
  function onDiskTargets(spec, assetSpec) {
6805
7150
  const out = /* @__PURE__ */ new Set();
6806
7151
  for (const e2 of buildManifest(assetSpec)) {
6807
7152
  if (!e2.applies(assetSpec)) continue;
6808
7153
  const t = classifyBaselineTarget(e2.target);
6809
- if (t !== null && existsSync21(join29(spec.projectDir, e2.target))) out.add(t.id);
7154
+ if (t !== null && existsSync21(join31(spec.projectDir, e2.target))) out.add(t.id);
6810
7155
  }
6811
7156
  return out;
6812
7157
  }
@@ -6831,7 +7176,7 @@ function renderInstallHeader(log, spec, mode) {
6831
7176
  );
6832
7177
  }
6833
7178
  log(infoRow("OPTIONS", formatOptions(spec)));
6834
- const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride);
7179
+ const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride, spec.keyExclude);
6835
7180
  if (finalAssets.length > 0) {
6836
7181
  const unreachable = finalAssets.filter((id) => {
6837
7182
  const asset = EXTERNAL_ASSETS.find((a) => a.id === id);
@@ -6935,7 +7280,7 @@ function createInstallRenderer(log, spec, verbose) {
6935
7280
  },
6936
7281
  onAssetResult: (result) => {
6937
7282
  const base = result.ok ? formatAssetMeta(result.asset, result.version) : result.message ?? "failed";
6938
- const outsideNote = result.ok && result.outside?.length ? ` \xB7 ${result.outside.map((o) => `${o.root}/ left as is (links outside the project: ${o.target})`).join(" \xB7 ")}` : "";
7283
+ const outsideNote = result.ok && result.outside?.length ? ` \xB7 ${result.outside.map(outsideSlotNote).join(" \xB7 ")}` : "";
6939
7284
  const meta = `${base}${outsideNote}`;
6940
7285
  log(` ${assetRow(result.ok ? "success" : "skip", result.asset.id, meta)}`);
6941
7286
  }
@@ -6993,23 +7338,23 @@ function residentEntriesOnDisk(spec) {
6993
7338
  const hasClaude = spec.cli.includes("claude");
6994
7339
  const surface = anchorSurface(spec.cli).scaffold;
6995
7340
  const mdFiles = (dir, kind, skip) => {
6996
- const abs = join30(spec.projectDir, dir);
7341
+ const abs = join32(spec.projectDir, dir);
6997
7342
  return existsSync22(abs) ? readdirSync6(abs, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md") && e2.name !== skip).map((e2) => ({
6998
7343
  source: `${kind}/${e2.name}`,
6999
7344
  // `target` 은 `residentCost` 의 분류 키다 — 실제 자리는 `file` 이 말한다.
7000
7345
  target: `.claude/${kind}/${e2.name}`,
7001
7346
  // 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 본문도,
7002
7347
  // 배포판에 더는 없는 파일도 실제로 상주하는 것은 이쪽이다.
7003
- file: join30(abs, e2.name)
7348
+ file: join32(abs, e2.name)
7004
7349
  })) : [];
7005
7350
  };
7006
- 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");
7007
- const slot = hasClaude ? join30(".claude", "skills") : join30(".agents", "skills");
7351
+ const rules = surface === "claude" ? mdFiles(join32(".claude", "rules"), "rules") : rulesInline(spec.cli) ? planned.filter((e2) => e2.target.startsWith(".claude/rules/")) : mdFiles(join32(".agents", "rules"), "rules", "uzys-harness.md");
7352
+ const slot = hasClaude ? join32(".claude", "skills") : join32(".agents", "skills");
7008
7353
  const skills = planned.filter((e2) => e2.target.startsWith(".claude/skills/")).map((e2) => ({
7009
7354
  ...e2,
7010
- file: join30(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
7355
+ file: join32(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
7011
7356
  })).filter((e2) => existsSync22(e2.file));
7012
- const agents = hasClaude ? mdFiles(join30(".claude", "agents"), "agents") : [];
7357
+ const agents = hasClaude ? mdFiles(join32(".claude", "agents"), "agents") : [];
7013
7358
  return [
7014
7359
  ...planned.filter(
7015
7360
  (e2) => !e2.target.startsWith(".claude/rules/") && !e2.target.startsWith(".claude/agents/") && !e2.target.startsWith(".claude/skills/")
@@ -7021,8 +7366,8 @@ function residentEntriesOnDisk(spec) {
7021
7366
  }
7022
7367
  function contextFilesOnDisk(spec) {
7023
7368
  const surface = anchorSurface(spec.cli).scaffold;
7024
- const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join30(".claude", "CLAUDE.md")] : ["AGENTS.md"];
7025
- return files.map((f) => join30(spec.projectDir, f));
7369
+ const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join32(".claude", "CLAUDE.md")] : ["AGENTS.md"];
7370
+ return files.map((f) => join32(spec.projectDir, f));
7026
7371
  }
7027
7372
  function rulesInsideAgentsMd(spec, cost) {
7028
7373
  if (anchorSurface(spec.cli).scaffold !== "agents-md" || !rulesInline(spec.cli)) return cost;
@@ -7289,7 +7634,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7289
7634
  const alreadyTrustedGlobally = (() => {
7290
7635
  try {
7291
7636
  if (!existsSync22(configPath)) return false;
7292
- return hasTrustEntry(readFileSync23(configPath, "utf8"), spec.projectDir);
7637
+ return hasTrustEntry(readFileSync24(configPath, "utf8"), spec.projectDir);
7293
7638
  } catch {
7294
7639
  return false;
7295
7640
  }
@@ -7314,7 +7659,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7314
7659
  const remote = (report.shared ?? []).find((f) => f.path === ".mcp.json")?.remote ?? [];
7315
7660
  for (const line of remoteMcpNextLines(remote, spec.cli)) log(`${" ".repeat(16)} ${line}`);
7316
7661
  const scaffoldFiles = scaffoldFilesForCli(spec.cli).filter(
7317
- (f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join30(spec.projectDir, f))
7662
+ (f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join32(spec.projectDir, f))
7318
7663
  );
7319
7664
  if (scaffoldFiles.length > 0) {
7320
7665
  const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
@@ -7330,7 +7675,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7330
7675
  }
7331
7676
  function hasFillPrompt(path) {
7332
7677
  try {
7333
- return readFileSync23(path, "utf8").includes("<!-- FILL:");
7678
+ return readFileSync24(path, "utf8").includes("<!-- FILL:");
7334
7679
  } catch {
7335
7680
  return false;
7336
7681
  }
@@ -7631,6 +7976,7 @@ function renderPhase1Rows(log, baseline, verbose = false) {
7631
7976
  )
7632
7977
  );
7633
7978
  }
7979
+ for (const row of leftAsIsRows(baseline.updateMode.externalSkillsLeftAsIs ?? [])) log(row);
7634
7980
  if (baseline.updateMode.externalSkillsFailed.length > 0) {
7635
7981
  const failed = baseline.updateMode.externalSkillsFailed;
7636
7982
  const head = failed[0];
@@ -7964,6 +8310,11 @@ function excludedKeyParts(f) {
7964
8310
  }
7965
8311
  return parts;
7966
8312
  }
8313
+ function leftAsIsRows(items) {
8314
+ return items.map(
8315
+ ({ id, outside }) => assetRow("skip", "external skills", `${id}: ${outside.map(outsideSlotNote).join(" \xB7 ")}`)
8316
+ );
8317
+ }
7967
8318
  function catalogRemovedRows(items) {
7968
8319
  return items.map((e2) => {
7969
8320
  const parts = [
@@ -8080,7 +8431,7 @@ function renderInterruptedInstall(err, e2, cwd = process.cwd()) {
8080
8431
  }
8081
8432
  const { path, error } = e2.record;
8082
8433
  err(
8083
- path !== null ? ` Already in place before it stopped \u2014 recorded in ${relative7(cwd, path) || path}:` : " Already in place before it stopped:"
8434
+ path !== null ? ` Already in place before it stopped \u2014 recorded in ${relative8(cwd, path) || path}:` : " Already in place before it stopped:"
8084
8435
  );
8085
8436
  for (const [key, n] of groups) {
8086
8437
  err(` ${key.endsWith("/") ? `${key} (${n} file${n === 1 ? "" : "s"})` : key}`);
@@ -8403,8 +8754,8 @@ function registerInstallCommand(cli) {
8403
8754
 
8404
8755
  // src/commands/list.ts
8405
8756
  init_esm_shims();
8406
- import { existsSync as existsSync23, readFileSync as readFileSync24 } from "fs";
8407
- import { join as join31, resolve as resolve3 } from "path";
8757
+ import { existsSync as existsSync23, readFileSync as readFileSync25 } from "fs";
8758
+ import { join as join33, resolve as resolve3 } from "path";
8408
8759
  function listAction(options = {}, deps = {}) {
8409
8760
  const log = deps.log ?? console.log;
8410
8761
  const err = deps.err ?? console.error;
@@ -8508,8 +8859,8 @@ function formatHarnessFileRows(log, projectDir) {
8508
8859
  const rows = [` ${c.dim(tops.length > 0 ? tops.join(" ") : "(none recorded)")}`];
8509
8860
  const rootMd = log.templates.rootClaudeMd;
8510
8861
  if (rootMd) {
8511
- const path = join31(projectDir, rootMd.path);
8512
- const modified = existsSync23(path) && hashContent(readFileSync24(path, "utf8")) !== rootMd.sha256;
8862
+ const path = join33(projectDir, rootMd.path);
8863
+ const modified = existsSync23(path) && hashContent(readFileSync25(path, "utf8")) !== rootMd.sha256;
8513
8864
  rows.push(
8514
8865
  modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
8515
8866
  );
@@ -8523,7 +8874,7 @@ var ROOT_FILE_CHANGE = {
8523
8874
  displaced: "\uBE44\uCF1C \uB460"
8524
8875
  };
8525
8876
  function formatRootFileRows(rootFiles, projectDir) {
8526
- const present = rootFiles.filter((f) => existsSync23(join31(projectDir, f.path)));
8877
+ const present = rootFiles.filter((f) => existsSync23(join33(projectDir, f.path)));
8527
8878
  const width = Math.max(0, ...present.map((f) => f.path.length));
8528
8879
  return present.map(
8529
8880
  (f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(ROOT_FILE_CHANGE[f.change])} ${c.dim(f.notes.join(" / "))}`
@@ -8544,9 +8895,9 @@ import {
8544
8895
  accessSync as accessSync2,
8545
8896
  existsSync as existsSync24,
8546
8897
  constants as fsConstants2,
8547
- lstatSync as lstatSync7,
8898
+ lstatSync as lstatSync8,
8548
8899
  readdirSync as readdirSync7,
8549
- readFileSync as readFileSync25,
8900
+ readFileSync as readFileSync26,
8550
8901
  readlinkSync as readlinkSync2,
8551
8902
  realpathSync as realpathSync4,
8552
8903
  rmdirSync as rmdirSync3,
@@ -8554,7 +8905,7 @@ import {
8554
8905
  statSync as statSync4,
8555
8906
  writeFileSync as writeFileSync8
8556
8907
  } from "fs";
8557
- import { dirname as dirname8, join as join32, relative as relative8, resolve as resolve4, sep as sep8 } from "path";
8908
+ import { dirname as dirname8, join as join34, relative as relative9, resolve as resolve4, sep as sep9 } from "path";
8558
8909
 
8559
8910
  // src/track-removal.ts
8560
8911
  init_esm_shims();
@@ -10001,7 +10352,7 @@ function hintFor(asset) {
10001
10352
  case "plugin":
10002
10353
  return "claude plugin uninstall --scope project";
10003
10354
  case "skill":
10004
- return asset.files === void 0 ? 'files not on record (older install) \u2014 nothing removed; only "Remove everything" clears it' : `removes the ${asset.files.length} file(s) on record`;
10355
+ return asset.files === void 0 ? 'files not on record (older install) \u2014 nothing removed; only "Remove everything" clears it' : asset.filesPartial === true ? `removes the ${asset.files.length} file(s) on record; files in other folders are not on record (older install) \u2014 left` : `removes the ${asset.files.length} file(s) on record`;
10005
10356
  case "npm":
10006
10357
  return "npm uninstall --save-dev";
10007
10358
  case "npx-run":
@@ -10263,10 +10614,10 @@ Proceed?`, initialValue: false });
10263
10614
  // src/commands/uninstall.ts
10264
10615
  var CODEX_CONFIG = ".codex/config.toml";
10265
10616
  function keptOutsideLinkLine(o, log) {
10266
- const head = ` ${c.yellow("\u2298")} ${o.link} \u2014 left as is: the link points outside the project (${o.link} \u2192 ${o.linkTarget}). Nothing behind it is removed`;
10617
+ const head = ` ${c.yellow("\u2298")} ${screenText(o.link)} \u2014 left as is: the link points outside the project (${screenText(o.link)} \u2192 ${screenText(o.linkTarget)}). Nothing behind it is removed`;
10267
10618
  const files = recordedUnder(log, o.link);
10268
10619
  if (files.length === 0) return `${head}; clean up harness files there by hand if intended.`;
10269
- const shown = files.slice(0, KEPT_LINK_FILES_SHOWN).join(" \xB7 ");
10620
+ const shown = files.slice(0, KEPT_LINK_FILES_SHOWN).map(screenText).join(" \xB7 ");
10270
10621
  const more = files.length > KEPT_LINK_FILES_SHOWN ? ` +${files.length - KEPT_LINK_FILES_SHOWN} more` : "";
10271
10622
  return `${head}; harness files there on record: ${shown}${more} \u2014 clean them up by hand if intended.`;
10272
10623
  }
@@ -10417,6 +10768,7 @@ function uninstallAction(options, deps = {}) {
10417
10768
  if (d2.verdict === "restore") journal.push(`put back ${d2.path}`);
10418
10769
  }
10419
10770
  for (const line of displacedLines(onlyDisplaced, false)) log(line);
10771
+ for (const line of byHandLines([], onlyDisplaced, projectDir, installLog)) log(line);
10420
10772
  } else {
10421
10773
  const done = removeHarnessFiles(installLog, projectDir, FULL_SCOPE, {
10422
10774
  rm: rmJ,
@@ -10493,7 +10845,7 @@ function unwritablePaths(ctx) {
10493
10845
  const project = ctx.targetAssets.filter((a) => a.scope === "project");
10494
10846
  if (project.some((a) => a.method === "npm")) need.add(".");
10495
10847
  const parentOf = (path) => {
10496
- const abs = join32(projectDir, path);
10848
+ const abs = join34(projectDir, path);
10497
10849
  if (!safeExists(abs)) return;
10498
10850
  const real = safeRealpath(abs);
10499
10851
  if (real !== null && isOutsideProject(projectDir, real)) return;
@@ -10505,7 +10857,7 @@ function unwritablePaths(ctx) {
10505
10857
  const recordedShared = new Set((installLog.portions ?? []).map((p2) => p2.path));
10506
10858
  const made = new Set((installLog.externalFiles ?? []).map((f) => f.path));
10507
10859
  for (const p2 of UNINSTALL_SHARED) {
10508
- if (recordedShared.has(p2) && existsSync24(join32(projectDir, p2))) need.add(p2);
10860
+ if (recordedShared.has(p2) && existsSync24(join34(projectDir, p2))) need.add(p2);
10509
10861
  if (made.has(p2)) parentOf(p2);
10510
10862
  }
10511
10863
  for (const p2 of harnessCandidates(installLog)) parentOf(p2);
@@ -10515,7 +10867,7 @@ function unwritablePaths(ctx) {
10515
10867
  }
10516
10868
  return [...need].filter((p2) => {
10517
10869
  try {
10518
- accessSync2(join32(projectDir, p2), fsConstants2.W_OK);
10870
+ accessSync2(join34(projectDir, p2), fsConstants2.W_OK);
10519
10871
  return false;
10520
10872
  } catch (e2) {
10521
10873
  return e2.code !== "ENOENT";
@@ -10524,7 +10876,7 @@ function unwritablePaths(ctx) {
10524
10876
  }
10525
10877
  function safeExists(abs) {
10526
10878
  try {
10527
- lstatSync7(abs);
10879
+ lstatSync8(abs);
10528
10880
  return true;
10529
10881
  } catch {
10530
10882
  return false;
@@ -10582,7 +10934,8 @@ function executeReverse(plan, log, logSurvives, journal) {
10582
10934
  failedIds.push(step.assetId);
10583
10935
  failed++;
10584
10936
  }
10585
- for (const note of result.notes ?? []) log(c.dim(` \u21B3 ${note}`));
10937
+ for (const note of [...result.notes ?? [], ...step.hints ?? []])
10938
+ log(c.dim(` \u21B3 ${note}`));
10586
10939
  }
10587
10940
  for (const line of noReverseLines(plan, logSurvives || failed > 0)) log(line);
10588
10941
  return { succeeded, failed, removedIds, failedIds };
@@ -10618,15 +10971,15 @@ function settleLog(ctx, io) {
10618
10971
  return true;
10619
10972
  }
10620
10973
  } else {
10621
- const recordDir = join32(projectDir, INSTALL_LOG_DIR);
10974
+ const recordDir = join34(projectDir, INSTALL_LOG_DIR);
10622
10975
  if (outsideLinkAt(projectDir, INSTALL_LOG_DIR) !== null) io.rm(recordDir);
10623
10976
  else {
10624
- for (const rel of RECORD_FILES) io.rm(join32(projectDir, rel));
10977
+ for (const rel of RECORD_FILES) io.rm(join34(projectDir, rel));
10625
10978
  pruneEmptyDirsUpward(projectDir, recordDir);
10626
10979
  }
10627
10980
  if (outsideLinkAt(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
10628
10981
  io.rm(legacyInstallLogPath(projectDir));
10629
- pruneEmptyDirsUpward(projectDir, join32(projectDir, LEGACY_INSTALL_LOG_DIR));
10982
+ pruneEmptyDirsUpward(projectDir, join34(projectDir, LEGACY_INSTALL_LOG_DIR));
10630
10983
  }
10631
10984
  io.log(` ${status.success("install log removed")}`);
10632
10985
  }
@@ -10642,7 +10995,7 @@ function afterFullRemoval(next, done) {
10642
10995
  if (back.size === 0) return next;
10643
10996
  const out = { ...next, rootFiles: withoutRestored(next.rootFiles, done.displaced) };
10644
10997
  const stays = (prefix) => (f) => !back.has(`${prefix}${f.path}`);
10645
- if (out.policyFiles !== void 0) out.policyFiles = out.policyFiles.filter(stays(CLAUDE_DIR3));
10998
+ if (out.policyFiles !== void 0) out.policyFiles = out.policyFiles.filter(stays(CLAUDE_DIR4));
10646
10999
  if (out.skillFiles !== void 0) out.skillFiles = out.skillFiles.filter(stays(SKILLS_DIR3));
10647
11000
  if (out.externalFiles !== void 0) out.externalFiles = out.externalFiles.filter(stays(""));
10648
11001
  const anchor = out.templates.rootClaudeMd;
@@ -10666,7 +11019,12 @@ function dryRunLines(plan, installLog, projectDir, assetsOnly, rootFiles, harnes
10666
11019
  if (plan.reverseSteps.length === 0 && copies.length === 0) {
10667
11020
  lines.push(c.dim(" (no project-scope assets to reverse)"));
10668
11021
  }
10669
- lines.push(...plan.reverseSteps.map((s) => ` \u25CB ${s.label}`));
11022
+ lines.push(
11023
+ ...plan.reverseSteps.flatMap((s) => [
11024
+ ` \u25CB ${s.label}`,
11025
+ ...(s.hints ?? []).map((hint) => c.dim(` \u21B3 ${hint}`))
11026
+ ])
11027
+ );
10670
11028
  lines.push(...globalCopyLines(copies, true));
10671
11029
  lines.push(
10672
11030
  assetsOnly ? ` \u25CB install log updated (${installLog.assets.length - plannedAssets(plan, installLog).length} asset(s) remain)` : " \u25CB remove install record (.uzys-agent-harness/ \u2014 \uC124\uCE58 \uAE30\uB85D\xB7\uCC28\uB2E8 \uB85C\uADF8\xB7update \uBC31\uC5C5 \uBAA9\uB85D)"
@@ -10690,7 +11048,10 @@ function dryRunLines(plan, installLog, projectDir, assetsOnly, rootFiles, harnes
10690
11048
  willBeGone: new Set(plannedAssetFiles(plan, installLog)),
10691
11049
  write: false
10692
11050
  });
10693
- lines.push(...displacedLines(preview, true));
11051
+ lines.push(
11052
+ ...displacedLines(preview, true),
11053
+ ...byHandLines([], preview, projectDir, installLog)
11054
+ );
10694
11055
  }
10695
11056
  lines.push(...advisoryLines(plan, projectDir, rootFiles, globalTrust), "");
10696
11057
  return lines;
@@ -10730,7 +11091,7 @@ function backupArtifactAdvisoryLines(projectDir) {
10730
11091
  }
10731
11092
  }
10732
11093
  for (const dir of BACKUP_SCAN_DIRS) {
10733
- const abs = join32(projectDir, dir);
11094
+ const abs = join34(projectDir, dir);
10734
11095
  if (!existsSync24(abs) || outsideLinkAt(projectDir, dir) !== null) continue;
10735
11096
  for (const rel of listFilesRecursive(abs)) {
10736
11097
  if (BACKUP_SUFFIX.test(rel)) found.push(`${dir}/${rel}`);
@@ -10741,16 +11102,16 @@ function backupArtifactAdvisoryLines(projectDir) {
10741
11102
  c.yellow(
10742
11103
  `[BACKUPS] \uD558\uB124\uC2A4\uAC00 \uB0A8\uAE34 \uBC31\uC5C5 ${found.length}\uAC74 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uB0B4\uC6A9 \uD655\uC778 \uD6C4 \uC0AD\uC81C):`
10743
11104
  ),
10744
- ...found.sort().map((f) => c.dim(` ${f}`))
11105
+ ...found.sort().map((f) => c.dim(` ${screenText(f)}`))
10745
11106
  ];
10746
11107
  }
10747
11108
  function rootFileAdvisoryLines(rootFiles, projectDir) {
10748
11109
  const header = "[ROOT] `.claude/` \uBC16\uC5D0 \uB0A8\uB294 \uAC83 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):";
10749
- const present = rootFiles.filter((f) => existsSync24(join32(projectDir, f.path)));
11110
+ const present = rootFiles.filter((f) => existsSync24(join34(projectDir, f.path)));
10750
11111
  if (present.length === 0) return [];
10751
11112
  const outside = /* @__PURE__ */ new Map();
10752
11113
  const inside = present.filter((f) => {
10753
- const o = outsideProjectTarget(projectDir, join32(projectDir, f.path));
11114
+ const o = outsideProjectTarget(projectDir, join34(projectDir, f.path));
10754
11115
  if (o === null) return true;
10755
11116
  const group = outside.get(o.link) ?? { link: o, files: [] };
10756
11117
  group.files.push(f.path);
@@ -10762,13 +11123,13 @@ function rootFileAdvisoryLines(rootFiles, projectDir) {
10762
11123
  c.yellow(header),
10763
11124
  ...[...outside.values()].map(
10764
11125
  ({ link: o, files }) => c.dim(
10765
- ` \xB7 ${o.link} \u2014 left as is: the link points outside the project (${o.link} \u2192 ${o.linkTarget}); harness files there: ${files.join(" \xB7 ")} (other projects linked to it may use them)`
11126
+ ` \xB7 ${screenText(o.link)} \u2014 left as is: the link points outside the project (${screenText(o.link)} \u2192 ${screenText(o.linkTarget)}); harness files there: ${files.map(screenText).join(" \xB7 ")} (other projects linked to it may use them)`
10766
11127
  )
10767
11128
  ),
10768
11129
  ...inside.flatMap((f) => [
10769
- c.dim(` \xB7 ${f.path} \u2014 ${rootFileMeaning(f, projectDir)}`),
11130
+ c.dim(` \xB7 ${screenText(f.path)} \u2014 ${rootFileMeaning(f, projectDir)}`),
10770
11131
  // displaced 의 notes 는 백업 경로 하나 — 위 줄이 이미 댔다
10771
- ...f.change !== "displaced" && f.notes.length > 0 ? [c.dim(` ${f.notes.join(" / ")}`)] : []
11132
+ ...f.change !== "displaced" && f.notes.length > 0 ? [c.dim(` ${f.notes.map(oneLine).join(" / ")}`)] : []
10772
11133
  ])
10773
11134
  ];
10774
11135
  }
@@ -10789,7 +11150,7 @@ function rootFileMeaning(f, projectDir) {
10789
11150
  case "displaced": {
10790
11151
  const backup = f.notes[0];
10791
11152
  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)";
10792
- return existsSync24(join32(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`;
11153
+ return existsSync24(join34(projectDir, backup)) ? `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC740 ${screenText(backup)} \uC5D0 \uC788\uB2E4` : `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC758 \uBC31\uC5C5(${screenText(backup)})\uC774 \uB354\uB294 \uC5C6\uB2E4`;
10793
11154
  }
10794
11155
  }
10795
11156
  }
@@ -10871,15 +11232,15 @@ function agentsMdRerenderSet(target, remaining, log, projectDir) {
10871
11232
  const set = agentsMdClis(remaining);
10872
11233
  if (set.length === 0) return null;
10873
11234
  const recorded2 = (log.externalFiles ?? []).some((f) => f.path === AGENTS_MD2) || (log.portions ?? []).some((p2) => p2.path === AGENTS_MD2);
10874
- if (!existsSync24(join32(projectDir, AGENTS_MD2))) return null;
10875
- if (!recorded2 && !isLegacyHarnessAgentsMd(readTextOrEmpty(join32(projectDir, AGENTS_MD2)))) {
11235
+ if (!existsSync24(join34(projectDir, AGENTS_MD2))) return null;
11236
+ if (!recorded2 && !isLegacyHarnessAgentsMd(readTextOrEmpty(join34(projectDir, AGENTS_MD2)))) {
10876
11237
  return null;
10877
11238
  }
10878
11239
  return set;
10879
11240
  }
10880
11241
  function readTextOrEmpty(abs) {
10881
11242
  try {
10882
- return readFileSync25(abs, "utf8");
11243
+ return readFileSync26(abs, "utf8");
10883
11244
  } catch {
10884
11245
  return "";
10885
11246
  }
@@ -10949,7 +11310,7 @@ function settleCliLog(ctx, target, remaining, done, anchorRemoved) {
10949
11310
  function forgetRemoved(next, done) {
10950
11311
  const out = { ...next };
10951
11312
  const kept = (prefix) => (f) => !done.forgotten.has(`${prefix}${f.path}`);
10952
- if (out.policyFiles !== void 0) out.policyFiles = out.policyFiles.filter(kept(CLAUDE_DIR3));
11313
+ if (out.policyFiles !== void 0) out.policyFiles = out.policyFiles.filter(kept(CLAUDE_DIR4));
10953
11314
  if (out.skillFiles !== void 0) out.skillFiles = out.skillFiles.filter(kept(SKILLS_DIR3));
10954
11315
  const survivors = (out.externalFiles ?? []).filter(kept(""));
10955
11316
  if (survivors.length > 0) out.externalFiles = survivors;
@@ -11003,9 +11364,9 @@ function runScopedRemoval(ctx, io, plan) {
11003
11364
  }
11004
11365
  for (const line of lines) io.log(line);
11005
11366
  const settled = plan.settle(done);
11006
- const agentsBefore = readTextOrEmpty(join32(projectDir, AGENTS_MD2));
11367
+ const agentsBefore = readTextOrEmpty(join34(projectDir, AGENTS_MD2));
11007
11368
  const rerender = plan.rerenderSet === null ? null : rerenderAgentsMd(ctx, plan.rerenderSet, settled);
11008
- const rewrote = rerender !== null && readTextOrEmpty(join32(projectDir, AGENTS_MD2)) !== agentsBefore;
11369
+ const rewrote = rerender !== null && readTextOrEmpty(join34(projectDir, AGENTS_MD2)) !== agentsBefore;
11009
11370
  if (rewrote) for (const line of rerenderLines(rerender)) io.log(line);
11010
11371
  const next = rerender === null ? settled : withRerender(settled, rerender);
11011
11372
  try {
@@ -11078,7 +11439,7 @@ function removeTrackAction(ctx, io) {
11078
11439
  }
11079
11440
  var TRACK_SCOPE = { shared: [], skeleton: [], folders: [], importBlock: false, anchor: false };
11080
11441
  function trackNotes(ctx, r, d2, write) {
11081
- const md = join32(ctx.projectDir, "CLAUDE.md");
11442
+ const md = join34(ctx.projectDir, "CLAUDE.md");
11082
11443
  const block = rootBlockNext(ctx, new Set(d2.removed));
11083
11444
  if (write && block !== null) {
11084
11445
  backupIfLossyUtf8(md);
@@ -11106,11 +11467,11 @@ function previewTrackRemoval(ctx, target) {
11106
11467
  }
11107
11468
  function rootBlockNext({ installLog, projectDir }, gone) {
11108
11469
  const anchor = installLog.templates?.rootClaudeMd?.path;
11109
- const md = join32(projectDir, "CLAUDE.md");
11470
+ const md = join34(projectDir, "CLAUDE.md");
11110
11471
  const text = safeExists(md) && outsideProjectTarget(projectDir, md) === null ? readTextOrEmpty(md) : "";
11111
- if (anchor === void 0 || !safeExists(join32(projectDir, anchor))) return null;
11472
+ if (anchor === void 0 || !safeExists(join34(projectDir, anchor))) return null;
11112
11473
  if (stripHarnessImport(text) === null) return null;
11113
- const stays = (id) => listFilesRecursive(join32(projectDir, SKILLS_DIR3, id)).some(
11474
+ const stays = (id) => listFilesRecursive(join34(projectDir, SKILLS_DIR3, id)).some(
11114
11475
  (f) => !gone.has(`${SKILLS_DIR3}${id}/${f}`)
11115
11476
  );
11116
11477
  const continuousSkills = CONTINUOUS_SKILLS.map((s) => s.id).filter(stays);
@@ -11121,8 +11482,8 @@ function settleTrackLog(ctx, target, remaining, done) {
11121
11482
  const { installLog, projectDir } = ctx;
11122
11483
  const tracks = installLog.spec.tracks.filter((t) => t !== target);
11123
11484
  const next = forgetRemoved({ ...installLog, spec: { ...installLog.spec, tracks } }, done);
11124
- const abs = join32(projectDir, INSTALLED_TRACKS);
11125
- const key = INSTALLED_TRACKS.slice(CLAUDE_DIR3.length);
11485
+ const abs = join34(projectDir, INSTALLED_TRACKS);
11486
+ const key = INSTALLED_TRACKS.slice(CLAUDE_DIR4.length);
11126
11487
  const prior = installLog.policyFiles?.find((f) => f.path === key)?.sha256 ?? hashContent(installedTracksText(installLog.spec.tracks.filter(isTrack)));
11127
11488
  const edited = safeExists(abs) && hashContent(readTextOrEmpty(abs)) !== prior;
11128
11489
  if (!installedClis(installLog).includes("claude")) return next;
@@ -11148,7 +11509,7 @@ var SKILLS_LOCK = "skills-lock.json";
11148
11509
  function withSkillsLock(rootFiles, assets, projectDir) {
11149
11510
  const out = [...rootFiles];
11150
11511
  const skills = assets.some((a) => a.method === "skill" && a.scope === "project");
11151
- if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync24(join32(projectDir, SKILLS_LOCK))) {
11512
+ if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync24(join34(projectDir, SKILLS_LOCK))) {
11152
11513
  out.push({ path: SKILLS_LOCK, change: "advisory", notes: [] });
11153
11514
  }
11154
11515
  return out;
@@ -11159,7 +11520,7 @@ function remnantFor(path, log, harnessRoot, projectDir, wiringGone = []) {
11159
11520
  const gone = (k2) => wiringGone.includes(`hooks/${k2.split("#")[1] ?? ""}`);
11160
11521
  return (disk) => {
11161
11522
  try {
11162
- const template = readFileSync25(join32(harnessRoot, "templates", "settings.json"), "utf8");
11523
+ const template = readFileSync26(join34(harnessRoot, "templates", "settings.json"), "utf8");
11163
11524
  const render = renderSettingsPortion(template, () => true);
11164
11525
  return [...legacySettingsSeed(disk, render, projectDir, { edited: true }).keys()].filter(
11165
11526
  (k2) => !k2.endsWith("{}") && !gone(k2)
@@ -11297,12 +11658,27 @@ function noReverseHints(asset, ctx) {
11297
11658
  return [".claude/ \uBC16\uC5D0 \uB9CC\uB4E0 \uD30C\uC77C(\uC608: _bmad/ \xB7 _openspec/)\uC740 \uC9C1\uC811 \uD655\uC778 \uD6C4 \uC9C0\uC6B4\uB2E4 \u2014 \uAE30\uB85D\uC5D0 \uC5C6\uB2E4"];
11298
11659
  }
11299
11660
  if (asset.method !== "skill") return [];
11300
- const name = asset.detail.skill;
11301
- const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => existsSync24(join32(ctx.projectDir, p2)));
11661
+ const left = unlistedFoldersLeft(asset, ctx.projectDir);
11302
11662
  return [
11303
11663
  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"
11304
11664
  ];
11305
11665
  }
11666
+ function unlistedFoldersLeft(asset, projectDir) {
11667
+ const name = asset.detail.skill;
11668
+ if (!isSkillName(name) || !isPlainPath(name)) return [];
11669
+ const listed = new Set((asset.files ?? []).map((f) => f.path.split("/").slice(0, 3).join("/")));
11670
+ return [".claude/skills", ".agents/skills"].filter((root) => !listed.has(`${root}/${name}`)).map((root) => `${root}/${name}/`).filter((p2) => existsSync24(join34(projectDir, p2)));
11671
+ }
11672
+ function partialHints(asset, projectDir) {
11673
+ const left = unlistedFoldersLeft(asset, projectDir);
11674
+ if (left.length > 0) {
11675
+ return [
11676
+ `other 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)`
11677
+ ];
11678
+ }
11679
+ const name = asset.detail.skill;
11680
+ return name === void 0 || !isSkillName(name) || !isPlainPath(name) ? ["other files it put down are not on record (installed by an older version) \u2014 left as is"] : [];
11681
+ }
11306
11682
  function buildProjectReverseStep(asset, ctx) {
11307
11683
  const { spawn } = ctx;
11308
11684
  switch (asset.method) {
@@ -11326,10 +11702,12 @@ ${r.stdout || ""}`;
11326
11702
  case "skill": {
11327
11703
  const files = asset.files;
11328
11704
  if (files === void 0) return null;
11705
+ const hints = asset.filesPartial === true ? partialHints(asset, ctx.projectDir) : [];
11329
11706
  return {
11330
11707
  assetId: asset.id,
11331
11708
  label: `remove ${asset.id} \u2014 ${files.length} file(s) on record`,
11332
- execute: () => removeToolFiles(files, ctx.projectDir, ctx.rm)
11709
+ execute: () => removeToolFiles(files, ctx.projectDir, ctx.rm),
11710
+ ...hints.length > 0 ? { hints } : {}
11333
11711
  };
11334
11712
  }
11335
11713
  case "npm": {
@@ -11380,7 +11758,7 @@ function buildGlobalAdvisoryCmd(asset) {
11380
11758
  return "(no standard reverse \u2014 manual)";
11381
11759
  }
11382
11760
  }
11383
- var CLAUDE_DIR3 = ".claude/";
11761
+ var CLAUDE_DIR4 = ".claude/";
11384
11762
  var LEGACY_LOG = `${LEGACY_INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`;
11385
11763
  var SKILLS_DIR3 = ".claude/skills/";
11386
11764
  var CLI_FOLDERS2 = [".claude", ".codex", ".opencode"];
@@ -11408,6 +11786,7 @@ function removeHarnessFiles(log, projectDir, scope, io) {
11408
11786
  legacyAgentsMd: [],
11409
11787
  unjudged: [],
11410
11788
  outside: [],
11789
+ offSlot: [],
11411
11790
  displaced: [],
11412
11791
  shared: [],
11413
11792
  retiredWiring: [],
@@ -11424,7 +11803,7 @@ function removeHarnessFiles(log, projectDir, scope, io) {
11424
11803
  }
11425
11804
  const judged = new Set(candidates);
11426
11805
  for (const path of legacyFileCandidates()) {
11427
- if (judged.has(path) || !scope.has(path) || !safeExists(join32(projectDir, path))) continue;
11806
+ if (judged.has(path) || !scope.has(path) || !safeExists(join34(projectDir, path))) continue;
11428
11807
  if (recorded(log, path, { isTarget }).state !== "none") continue;
11429
11808
  removeRecordedFile(path, { log, projectDir, io, out, isTarget, pruneFrom });
11430
11809
  }
@@ -11450,9 +11829,9 @@ function removeHarnessFiles(log, projectDir, scope, io) {
11450
11829
  write: io.write
11451
11830
  });
11452
11831
  if (io.write) {
11453
- for (const p2 of pruneFrom) pruneEmptyDirsUpward(projectDir, dirname8(join32(projectDir, p2)));
11832
+ for (const p2 of pruneFrom) pruneEmptyDirsUpward(projectDir, dirname8(join34(projectDir, p2)));
11454
11833
  for (const d2 of scope.skeleton) {
11455
- const abs = join32(projectDir, d2);
11834
+ const abs = join34(projectDir, d2);
11456
11835
  if (outsideProjectTarget(projectDir, abs) !== null) continue;
11457
11836
  pruneEmptyDirsUpward(projectDir, abs);
11458
11837
  }
@@ -11461,7 +11840,7 @@ function removeHarnessFiles(log, projectDir, scope, io) {
11461
11840
  }
11462
11841
  function harnessCandidates(log) {
11463
11842
  const paths = [
11464
- ...(log.policyFiles ?? []).map((f) => `${CLAUDE_DIR3}${f.path}`),
11843
+ ...(log.policyFiles ?? []).map((f) => `${CLAUDE_DIR4}${f.path}`),
11465
11844
  ...(log.skillFiles ?? []).map((f) => `${SKILLS_DIR3}${f.path}`),
11466
11845
  ...(log.externalFiles ?? []).map((f) => f.path),
11467
11846
  // 헬퍼 — writer 기록은 `externalFiles` 에 sha 로 적지만 옛 기록(≤v26.161.0)엔 항목이 없다. `recorded()` 가 가른다
@@ -11488,17 +11867,17 @@ function helperSources() {
11488
11867
  function templateNext(harnessRoot, log, path) {
11489
11868
  if (path === INSTALLED_TRACKS) return installedTracksText(log.spec.tracks.filter(isTrack));
11490
11869
  const helper = helperSources().get(path);
11491
- if (helper === void 0 && !path.startsWith(CLAUDE_DIR3)) return null;
11870
+ if (helper === void 0 && !path.startsWith(CLAUDE_DIR4)) return null;
11492
11871
  try {
11493
- const source = helper ?? path.slice(CLAUDE_DIR3.length);
11494
- return readFileSync25(join32(harnessRoot, "templates", source), "utf8");
11872
+ const source = helper ?? path.slice(CLAUDE_DIR4.length);
11873
+ return readFileSync26(join34(harnessRoot, "templates", source), "utf8");
11495
11874
  } catch {
11496
11875
  return null;
11497
11876
  }
11498
11877
  }
11499
11878
  function removeRecordedFile(path, ctx) {
11500
11879
  const { log, projectDir, io, out } = ctx;
11501
- const abs = join32(projectDir, path);
11880
+ const abs = join34(projectDir, path);
11502
11881
  let foreign;
11503
11882
  try {
11504
11883
  foreign = foreignOwnedTarget(projectDir, path);
@@ -11515,9 +11894,14 @@ function removeRecordedFile(path, ctx) {
11515
11894
  out.outside.push(outside);
11516
11895
  return;
11517
11896
  }
11897
+ if (!inHarnessSlot(projectDir, path, HARNESS_FILES, { follow: true })) {
11898
+ out.offSlot.push(path);
11899
+ out.forgotten.add(path);
11900
+ return;
11901
+ }
11518
11902
  let st2;
11519
11903
  try {
11520
- st2 = lstatSync7(abs, { throwIfNoEntry: false });
11904
+ st2 = lstatSync8(abs, { throwIfNoEntry: false });
11521
11905
  } catch {
11522
11906
  out.unjudged.push(path);
11523
11907
  return;
@@ -11535,7 +11919,7 @@ function removeRecordedFile(path, ctx) {
11535
11919
  return;
11536
11920
  }
11537
11921
  if (st2.isSymbolicLink()) effective = real;
11538
- disk = readFileSync25(effective, "utf8");
11922
+ disk = readFileSync26(effective, "utf8");
11539
11923
  } catch {
11540
11924
  out.unjudged.push(path);
11541
11925
  return;
@@ -11553,7 +11937,7 @@ function removeRecordedFile(path, ctx) {
11553
11937
  if (verdict.verdict !== "remove" && verdict.verdict !== "backup+remove") return;
11554
11938
  let backup = null;
11555
11939
  if (verdict.verdict === "backup+remove") {
11556
- backup = io.write ? relative8(projectDir, backupFile(effective)).split(sep8).join("/") : `${relative8(projectDir, effective).split(sep8).join("/")}.backup-<time>`;
11940
+ backup = io.write ? relative9(projectDir, backupFile(effective)).split(sep9).join("/") : `${relative9(projectDir, effective).split(sep9).join("/")}.backup-<time>`;
11557
11941
  }
11558
11942
  if (io.write) {
11559
11943
  if (effective !== abs) io.rm(abs);
@@ -11582,11 +11966,11 @@ function mayReclaimLegacy(log, projectDir, path) {
11582
11966
  function removeLegacyHookWiring(log, projectDir, scope, io, removed) {
11583
11967
  if (!scope.has(SETTINGS_TARGET) || !installedClis(log).includes("claude")) return [];
11584
11968
  if ((log.portions ?? []).some((p2) => p2.path === SETTINGS_TARGET)) return [];
11585
- const abs = join32(projectDir, SETTINGS_TARGET);
11969
+ const abs = join34(projectDir, SETTINGS_TARGET);
11586
11970
  if (!safeExists(abs) || outsideProjectTarget(projectDir, abs) !== null) return [];
11587
- const claudeDir = join32(projectDir, ".claude");
11971
+ const claudeDir = join34(projectDir, ".claude");
11588
11972
  const gone = LEGACY_HOOK_REFS.filter(
11589
- (r) => !RETIRED_HOOK_WIRING_REFS.includes(r) && (removed.includes(`${CLAUDE_DIR3}${r}`) || !safeExists(join32(claudeDir, r)))
11973
+ (r) => !RETIRED_HOOK_WIRING_REFS.includes(r) && (removed.includes(`${CLAUDE_DIR4}${r}`) || !safeExists(join34(claudeDir, r)))
11590
11974
  );
11591
11975
  return removeRetiredHookRefs(abs, claudeDir, gone, { evenIfScriptExists: true, write: io.write });
11592
11976
  }
@@ -11607,7 +11991,7 @@ function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
11607
11991
  };
11608
11992
  for (const path of paths) {
11609
11993
  if (!relevant(path)) continue;
11610
- const abs = join32(projectDir, path);
11994
+ const abs = join34(projectDir, path);
11611
11995
  if (safeExists(abs)) {
11612
11996
  const o = outsideProjectTarget(projectDir, abs);
11613
11997
  if (o !== null) {
@@ -11618,7 +12002,7 @@ function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
11618
12002
  if (path === SETTINGS_TARGET && safeExists(abs)) {
11619
12003
  out.retiredWiring = removeRetiredHookRefs(
11620
12004
  abs,
11621
- join32(projectDir, ".claude"),
12005
+ join34(projectDir, ".claude"),
11622
12006
  RETIRED_HOOK_WIRING_REFS,
11623
12007
  { evenIfScriptExists: true, write: io.write }
11624
12008
  );
@@ -11646,7 +12030,7 @@ function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
11646
12030
  }
11647
12031
  }
11648
12032
  function removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom) {
11649
- const abs = join32(projectDir, path);
12033
+ const abs = join34(projectDir, path);
11650
12034
  if (!existsSync24(abs)) {
11651
12035
  out.forgotten.add(path);
11652
12036
  return true;
@@ -11659,8 +12043,8 @@ function removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom) {
11659
12043
  out.unjudged.push(path);
11660
12044
  return true;
11661
12045
  }
11662
- if (lstatSync7(abs).isSymbolicLink()) effective = real;
11663
- current = readFileSync25(effective, "utf8");
12046
+ if (lstatSync8(abs).isSymbolicLink()) effective = real;
12047
+ current = readFileSync26(effective, "utf8");
11664
12048
  } catch {
11665
12049
  out.unjudged.push(path);
11666
12050
  return true;
@@ -11728,14 +12112,16 @@ function displacedLines(displaced, preview) {
11728
12112
  const restore = displaced.filter((d2) => d2.verdict === "restore");
11729
12113
  if (preview && restore.length > 0) {
11730
12114
  lines.push(
11731
- ` \u25CB put back ${restore.length} file(s) of yours (displaced): ${restore.map((d2) => d2.path).join(" \xB7 ")}`
12115
+ ` \u25CB put back ${restore.length} file(s) of yours (displaced): ${restore.map((d2) => screenText(d2.path)).join(" \xB7 ")}`
11732
12116
  );
11733
12117
  }
11734
12118
  for (const d2 of displaced) {
11735
12119
  if (d2.verdict === "restore" && !preview) {
11736
- lines.push(` ${status.success(`put back ${d2.path} (from ${d2.backup})`)}`);
12120
+ lines.push(
12121
+ ` ${status.success(`put back ${screenText(d2.path)} (from ${screenText(String(d2.backup))})`)}`
12122
+ );
11737
12123
  } else if (d2.verdict === "advise") {
11738
- lines.push(` ${c.yellow("\u2298")} ${d2.path} \u2014 ${d2.line}`);
12124
+ lines.push(` ${c.yellow("\u2298")} ${screenText(d2.path)} \u2014 ${d2.line}`);
11739
12125
  }
11740
12126
  }
11741
12127
  return lines;
@@ -11817,6 +12203,7 @@ function harnessRemovalLines(r, log, projectDir, scope, preview, alsoGone = [])
11817
12203
  );
11818
12204
  }
11819
12205
  lines.push(...outsideLines(r.outside, log));
12206
+ lines.push(...offSlotLines(r.offSlot, projectDir));
11820
12207
  for (const path of r.kept) {
11821
12208
  lines.push(
11822
12209
  ` ${c.yellow("\u2298")} ${path} kept \u2014 modified since install. Remove manually if intended.`
@@ -11836,13 +12223,14 @@ function harnessRemovalLines(r, log, projectDir, scope, preview, alsoGone = [])
11836
12223
  }
11837
12224
  lines.push(...sharedStripLines(r.shared, preview));
11838
12225
  lines.push(...leftFolderLines(r, log, projectDir, scope, preview, alsoGone));
12226
+ lines.push(...byHandLines(r.offSlot, r.displaced, projectDir, log));
11839
12227
  return lines;
11840
12228
  }
11841
12229
  function outsideLines(outside, log) {
11842
12230
  const byLink = /* @__PURE__ */ new Map();
11843
12231
  for (const o of outside) if (!byLink.has(o.link)) byLink.set(o.link, o);
11844
12232
  return [...byLink.values()].map(
11845
- (o) => o.link === o.path ? ` ${c.yellow("\u2298")} ${o.path} kept \u2014 link target is outside the project (${o.target}). Not touched; remove manually if intended.` : keptOutsideLinkLine(o, log)
12233
+ (o) => o.link === o.path ? ` ${c.yellow("\u2298")} ${screenText(o.path)} kept \u2014 link target is outside the project (${screenText(o.target)}). Not touched; remove manually if intended.` : keptOutsideLinkLine(o, log)
11846
12234
  );
11847
12235
  }
11848
12236
  function leftFolderLines(r, log, projectDir, scope, preview, alsoGone) {
@@ -11851,7 +12239,7 @@ function leftFolderLines(r, log, projectDir, scope, preview, alsoGone) {
11851
12239
  preview ? [...r.removed, ...r.shared.filter((s) => s.fileRemoved).map((s) => s.path), ...alsoGone] : []
11852
12240
  );
11853
12241
  const back = preview ? r.displaced.filter((d2) => d2.verdict === "restore").map((d2) => d2.path) : [];
11854
- const said = (full) => r.unjudged.includes(full) || r.kept.includes(full) || r.outside.some((o) => o.path === full || full.startsWith(`${o.link}/`));
12242
+ const said = (full) => r.unjudged.includes(full) || r.kept.includes(full) || r.offSlot.includes(full) || r.outside.some((o) => o.path === full || full.startsWith(`${o.link}/`));
11855
12243
  const whose = "not on the install record \u2014 yours, or left by an older harness version; delete by hand any that are not yours";
11856
12244
  const lines = [];
11857
12245
  for (const dir of scope.folders) {
@@ -11884,19 +12272,19 @@ function yoursUnder(projectDir, dir) {
11884
12272
  }
11885
12273
  for (const e2 of entries) {
11886
12274
  const name = `${prefix}${e2.name}`;
11887
- if (e2.isDirectory()) walk(join32(abs2, e2.name), `${name}/`);
12275
+ if (e2.isDirectory()) walk(join34(abs2, e2.name), `${name}/`);
11888
12276
  else if (BACKUP_SUFFIX.test(e2.name)) continue;
11889
12277
  else if (e2.isSymbolicLink()) {
11890
12278
  let target = "?";
11891
12279
  try {
11892
- target = readlinkSync2(join32(abs2, e2.name));
12280
+ target = readlinkSync2(join34(abs2, e2.name));
11893
12281
  } catch {
11894
12282
  }
11895
12283
  out.push({ name, shown: `${name} (your link \u2192 ${target})` });
11896
12284
  } else out.push({ name, shown: name });
11897
12285
  }
11898
12286
  };
11899
- const abs = join32(projectDir, dir);
12287
+ const abs = join34(projectDir, dir);
11900
12288
  try {
11901
12289
  if (!statSync4(abs).isDirectory()) return [];
11902
12290
  } catch {
@@ -11908,7 +12296,7 @@ function yoursUnder(projectDir, dir) {
11908
12296
  function assetsLeftLines(log, projectDir, scope) {
11909
12297
  const lines = [];
11910
12298
  for (const a of log.assets) {
11911
- const here = (a.files ?? []).map((f) => f.path).filter((p2) => scope.has(p2) && safeExists(join32(projectDir, p2)));
12299
+ const here = (a.files ?? []).map((f) => f.path).filter((p2) => scope.has(p2) && safeExists(join34(projectDir, p2)));
11912
12300
  if (here.length === 0) continue;
11913
12301
  const slots = [...new Set(here.map((p2) => p2.split("/").slice(0, 3).join("/")))].sort();
11914
12302
  lines.push(
@@ -11920,13 +12308,16 @@ function assetsLeftLines(log, projectDir, scope) {
11920
12308
  function removeAnchor(log, projectDir, io) {
11921
12309
  const rootMd = log.templates?.rootClaudeMd;
11922
12310
  if (!rootMd) return { removed: false, lines: [] };
11923
- const abs = join32(projectDir, rootMd.path);
12311
+ const abs = join34(projectDir, rootMd.path);
11924
12312
  if (!existsSync24(abs)) return { removed: false, lines: [] };
11925
12313
  const outside = outsideProjectTarget(projectDir, abs);
11926
12314
  if (outside !== null) return { removed: false, lines: outsideLines([outside], log) };
12315
+ if (!inHarnessSlot(projectDir, rootMd.path, ANCHOR_FILES, { follow: true })) {
12316
+ return { removed: false, lines: [offSlotLine(rootMd.path, "names it as the harness anchor")] };
12317
+ }
11927
12318
  let disk;
11928
12319
  try {
11929
- disk = readFileSync25(abs, "utf8");
12320
+ disk = readFileSync26(abs, "utf8");
11930
12321
  } catch {
11931
12322
  return {
11932
12323
  removed: false,
@@ -11959,7 +12350,7 @@ function removeAnchor(log, projectDir, io) {
11959
12350
  ]
11960
12351
  };
11961
12352
  }
11962
- const backup = edited ? relative8(projectDir, backupFile(abs)).split(sep8).join("/") : null;
12353
+ const backup = edited ? relative9(projectDir, backupFile(abs)).split(sep9).join("/") : null;
11963
12354
  io.rm(abs);
11964
12355
  return {
11965
12356
  removed: true,
@@ -11971,7 +12362,7 @@ function removeAnchor(log, projectDir, io) {
11971
12362
  var AGENTS_MD2 = "AGENTS.md";
11972
12363
  function readAgentsMdTemplate(harnessRoot) {
11973
12364
  try {
11974
- return readFileSync25(join32(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
12365
+ return readFileSync26(join34(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
11975
12366
  } catch {
11976
12367
  return null;
11977
12368
  }
@@ -11997,14 +12388,14 @@ function agentsMdRemainder(current, harnessRoot) {
11997
12388
  return { ok: true, remainder: strip.remainder, strip };
11998
12389
  }
11999
12390
  function removeUnrecordedLegacyAgentsMd(path, projectDir, io, out, pruneFrom) {
12000
- const abs = join32(projectDir, path);
12391
+ const abs = join34(projectDir, path);
12001
12392
  let effective = abs;
12002
12393
  let current;
12003
12394
  try {
12004
12395
  const real = realpathSync4(abs);
12005
12396
  if (!statSync4(real).isFile()) return false;
12006
- if (lstatSync7(abs).isSymbolicLink()) effective = real;
12007
- current = readFileSync25(effective, "utf8");
12397
+ if (lstatSync8(abs).isSymbolicLink()) effective = real;
12398
+ current = readFileSync26(effective, "utf8");
12008
12399
  } catch {
12009
12400
  return false;
12010
12401
  }
@@ -12022,7 +12413,7 @@ function removeLegacyAgentsMd(path, abs, effective, strip, ctx) {
12022
12413
  let backup = `${path}.backup-<time>`;
12023
12414
  try {
12024
12415
  if (io.write) {
12025
- backup = relative8(projectDir, backupFile(effective)).split(sep8).join("/");
12416
+ backup = relative9(projectDir, backupFile(effective)).split(sep9).join("/");
12026
12417
  if (strip.remainder !== null) {
12027
12418
  writeFileSync8(effective, neutralizeScaffoldBanner(strip.remainder), "utf8");
12028
12419
  } else {
@@ -12061,14 +12452,114 @@ function globalCopyLines(copies, preview) {
12061
12452
  ];
12062
12453
  });
12063
12454
  }
12455
+ var ASSET_SLOTS = skillFolders(SKILL_ROOTS);
12456
+ function offSlotGroups(paths, projectDir) {
12457
+ const literal = [];
12458
+ const byLink = /* @__PURE__ */ new Map();
12459
+ for (const path of paths) {
12460
+ const link = linkOnPath(projectDir, path);
12461
+ if (link === null) {
12462
+ literal.push(path);
12463
+ continue;
12464
+ }
12465
+ const g = byLink.get(link.link) ?? { target: link.target, files: [] };
12466
+ g.files.push(path);
12467
+ byLink.set(link.link, g);
12468
+ }
12469
+ return { literal, byLink };
12470
+ }
12471
+ function offSlotLines(paths, projectDir) {
12472
+ const { literal, byLink } = offSlotGroups(paths, projectDir);
12473
+ return [
12474
+ ...literal.map((path) => offSlotLine(path, "lists it")),
12475
+ ...[...byLink].map(
12476
+ ([link, g]) => ` ${c.yellow("\u2298")} ${screenText(link)} \u2014 your link (\u2192 ${screenText(g.target)}) leads outside the places this tool installs to; left ${g.files.length} file(s) on the install record under it. Not touched \u2014 remove by hand if intended.`
12477
+ )
12478
+ ];
12479
+ }
12480
+ function byHandLines(offSlot, displaced, projectDir, log) {
12481
+ let hidden = 0;
12482
+ let bundleTargets;
12483
+ const stands = (path, confirmed) => {
12484
+ if (isPlainPath(path) && confirmed()) return true;
12485
+ hidden += 1;
12486
+ return false;
12487
+ };
12488
+ const back = displaced.flatMap((d2) => d2.handBack === void 0 ? [] : [d2.handBack]);
12489
+ const movable = back.filter((b2) => stands(b2.from, () => true) && stands(b2.to, () => b2.safe));
12490
+ const links = [...offSlotGroups(offSlot, projectDir).byLink].flatMap(([link, g]) => {
12491
+ const shown = g.files.filter((f) => f !== link).filter(
12492
+ (f) => stands(f, () => {
12493
+ bundleTargets ??= recordedTrackTargets(log);
12494
+ return isPlainPath(link) && holdsHarnessBytes(log, projectDir, f, bundleTargets);
12495
+ })
12496
+ ).sort();
12497
+ return shown.length > 0 ? [{ link, files: shown }] : [];
12498
+ });
12499
+ if (movable.length === 0 && links.length === 0 && hidden === 0) return [];
12500
+ const yours = new Set(movable.map((b2) => b2.to));
12501
+ const lines = [
12502
+ ` ${c.yellow("\u2298")} left for you to finish by hand (nothing here was touched \u2014 run from the project folder):`
12503
+ ];
12504
+ if (movable.length > 0) {
12505
+ lines.push(
12506
+ c.dim(
12507
+ " your files this tool moved aside \u2014 put each back (only an empty place or a harness copy is overwritten):"
12508
+ )
12509
+ );
12510
+ for (const b2 of movable) lines.push(c.dim(` mv -i ${b2.from} ${b2.to}`));
12511
+ }
12512
+ for (const g of links) {
12513
+ lines.push(
12514
+ c.dim(
12515
+ ` listed on the install record under your link ${g.link} \u2014 check, then remove if intended:`
12516
+ )
12517
+ );
12518
+ for (const file of g.files) {
12519
+ lines.push(
12520
+ c.dim(
12521
+ yours.has(file) ? ` ${file} (your own file goes back here \u2014 do not delete)` : ` ${file}`
12522
+ )
12523
+ );
12524
+ }
12525
+ }
12526
+ if (hidden > 0) {
12527
+ lines.push(
12528
+ c.dim(
12529
+ ` not shown \u2014 ${hidden} entr${hidden === 1 ? "y" : "ies"} on the install record that this tool cannot confirm it wrote or cannot safely put back (a name it never writes, or contents that differ from what it installed): inspect the record, and look for *.backup-* files`
12530
+ )
12531
+ );
12532
+ }
12533
+ return lines;
12534
+ }
12535
+ function isPlainPath(path) {
12536
+ return path.split("/").every((seg) => /^[A-Za-z0-9._][A-Za-z0-9._-]*$/.test(seg) && seg !== "." && seg !== "..");
12537
+ }
12538
+ function linkOnPath(projectDir, path) {
12539
+ const parts = path.split("/");
12540
+ for (let i = 1; i <= parts.length; i++) {
12541
+ const rel = parts.slice(0, i).join("/");
12542
+ try {
12543
+ if (lstatSync8(join34(projectDir, rel), { throwIfNoEntry: false })?.isSymbolicLink() === true) {
12544
+ return { link: rel, target: readlinkSync2(join34(projectDir, rel)) };
12545
+ }
12546
+ } catch {
12547
+ return null;
12548
+ }
12549
+ }
12550
+ return null;
12551
+ }
12552
+ function offSlotLine(path, what) {
12553
+ return ` ${c.yellow("\u2298")} ${screenText(path)} left \u2014 the install record ${what}, but it is (or leads) outside the places this tool installs to. Not touched.`;
12554
+ }
12064
12555
  function removeToolFiles(files, projectDir, rm) {
12065
12556
  const notes = [];
12066
12557
  const removed = [];
12067
12558
  let failed = 0;
12068
- const pairs = new Set(files.filter((f) => isAppleDoublePath(join32(projectDir, f.path))));
12559
+ const pairs = new Set(files.filter((f) => isAppleDoublePath(join34(projectDir, f.path))));
12069
12560
  for (const file of files) {
12070
12561
  const { path, sha256 } = file;
12071
- const abs = join32(projectDir, path);
12562
+ const abs = join34(projectDir, path);
12072
12563
  if (pairs.has(file)) {
12073
12564
  try {
12074
12565
  rm(abs);
@@ -12078,7 +12569,7 @@ function removeToolFiles(files, projectDir, rm) {
12078
12569
  }
12079
12570
  let isLink;
12080
12571
  try {
12081
- isLink = lstatSync7(abs).isSymbolicLink();
12572
+ isLink = lstatSync8(abs).isSymbolicLink();
12082
12573
  } catch {
12083
12574
  continue;
12084
12575
  }
@@ -12091,10 +12582,16 @@ function removeToolFiles(files, projectDir, rm) {
12091
12582
  notes.push(`left ${path} \u2014 it is a link now, not the copy that was installed`);
12092
12583
  continue;
12093
12584
  }
12585
+ if (!inHarnessSlot(projectDir, path, ASSET_SLOTS)) {
12586
+ notes.push(
12587
+ `left ${path} \u2014 the install record lists it for this asset, but it is (or leads) outside the skill folders this asset installs to`
12588
+ );
12589
+ continue;
12590
+ }
12094
12591
  try {
12095
- if (hashContent(readFileSync25(abs, "utf8")) !== sha256) {
12592
+ if (hashContent(readFileSync26(abs, "utf8")) !== sha256) {
12096
12593
  const backup = backupFile(abs);
12097
- notes.push(`backed up ${path} \u2192 ${relative8(projectDir, backup)} (changed since install)`);
12594
+ notes.push(`backed up ${path} \u2192 ${relative9(projectDir, backup)} (changed since install)`);
12098
12595
  }
12099
12596
  rm(abs);
12100
12597
  removed.push(path);
@@ -12103,14 +12600,14 @@ function removeToolFiles(files, projectDir, rm) {
12103
12600
  notes.push(`left ${path} \u2014 ${e2 instanceof Error ? e2.message : String(e2)}`);
12104
12601
  }
12105
12602
  }
12106
- for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname8(join32(projectDir, path)));
12603
+ for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname8(join34(projectDir, path)));
12107
12604
  return failed > 0 ? { ok: false, message: `${failed} file(s) could not be removed`, notes } : { ok: true, notes };
12108
12605
  }
12109
12606
  function pruneEmptyDirsUpward(projectDir, startDir) {
12110
12607
  let cursor = startDir;
12111
12608
  while (true) {
12112
- const rel = relative8(projectDir, cursor);
12113
- if (rel === "" || rel.startsWith(`..${sep8}`) || rel === "..") return;
12609
+ const rel = relative9(projectDir, cursor);
12610
+ if (rel === "" || rel.startsWith(`..${sep9}`) || rel === "..") return;
12114
12611
  try {
12115
12612
  if (readdirSync7(cursor).length > 0) return;
12116
12613
  rmdirSync3(cursor);
@@ -12127,8 +12624,8 @@ function stripRootImport(projectDir) {
12127
12624
  if (withoutImport === null) return false;
12128
12625
  const stripped = neutralizeScaffoldBanner(withoutImport);
12129
12626
  try {
12130
- backupIfLossyUtf8(join32(projectDir, "CLAUDE.md"));
12131
- writeFileSync8(join32(projectDir, "CLAUDE.md"), stripped, "utf8");
12627
+ backupIfLossyUtf8(join34(projectDir, "CLAUDE.md"));
12628
+ writeFileSync8(join34(projectDir, "CLAUDE.md"), stripped, "utf8");
12132
12629
  return true;
12133
12630
  } catch {
12134
12631
  return false;
@@ -12150,13 +12647,13 @@ function importOutsideLine(o, anchorGone) {
12150
12647
  return ` ${c.yellow("\u2298")} CLAUDE.md kept \u2014 link target is outside the project (${o.target}). Not touched \u2014 its harness @import block (${HARNESS_IMPORT_LINE}) is left in it${pointing} \u2014 delete that block by hand.`;
12151
12648
  }
12152
12649
  function rootClaudeMdOutside(projectDir) {
12153
- return outsideProjectTarget(projectDir, join32(projectDir, "CLAUDE.md"));
12650
+ return outsideProjectTarget(projectDir, join34(projectDir, "CLAUDE.md"));
12154
12651
  }
12155
12652
  function readRootClaudeMd(projectDir) {
12156
- const path = join32(projectDir, "CLAUDE.md");
12653
+ const path = join34(projectDir, "CLAUDE.md");
12157
12654
  if (!existsSync24(path)) return null;
12158
12655
  try {
12159
- return readFileSync25(path, "utf8");
12656
+ return readFileSync26(path, "utf8");
12160
12657
  } catch {
12161
12658
  return null;
12162
12659
  }
@@ -12351,7 +12848,8 @@ function classifyUpdateIntent(log, confirmed, legacyTracks = []) {
12351
12848
  const recordClis = recordedClis(log);
12352
12849
  if (!sameSet(confirmed.tracks, recordTracks)) return "add";
12353
12850
  if (!sameSet(confirmed.cli, recordClis)) return "add";
12354
- const out = excludedIds(log);
12851
+ const recordedOut = excludedIds(log);
12852
+ const out = /* @__PURE__ */ new Set([...recordedOut, ...droppedMcpAssetIds(resolveBundleRoot(), recordedOut)]);
12355
12853
  const recorded2 = log?.assets ?? [];
12356
12854
  const covered = coveredByRecord(log, legacyTracks);
12357
12855
  const picked = new Set(confirmed.assetIds);
@@ -12699,7 +13197,8 @@ async function confirmUpdate(input) {
12699
13197
  function unchecked(log, tracks, assetIds, baselineIds) {
12700
13198
  const checked = /* @__PURE__ */ new Set([...assetIds, ...baselineIds]);
12701
13199
  const offered = new Set(listBaselineTargets({ tracks }).map((t) => t.id));
12702
- return [...excludedIds(log)].filter((id) => !checked.has(id)).filter((id) => !id.startsWith(BASELINE_PREFIX) || offered.has(id)).sort();
13200
+ const rechecked = (id) => isKeyId(id) && droppedMcpAssetIds(resolveBundleRoot(), /* @__PURE__ */ new Set([id])).some((a) => checked.has(a));
13201
+ return [...excludedIds(log)].filter((id) => !checked.has(id) && !rechecked(id)).filter((id) => !id.startsWith(BASELINE_PREFIX) || offered.has(id)).sort();
12703
13202
  }
12704
13203
  function annotate(summary, notes) {
12705
13204
  return summary.split("\n").map((line) => {
@@ -12734,7 +13233,7 @@ function formatSummary(spec) {
12734
13233
  `CLI: ${spec.cli.join(" \xB7 ")}`,
12735
13234
  `Target: ${spec.projectDir}`
12736
13235
  ];
12737
- const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride);
13236
+ const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride, spec.keyExclude);
12738
13237
  if (finalAssets.length > 0) {
12739
13238
  const unreachable = finalAssets.filter((id) => {
12740
13239
  const asset = EXTERNAL_ASSETS.find((a) => a.id === id);