@uzysjung/agent-harness 26.176.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
@@ -133,6 +133,7 @@ import {
133
133
  selectedMcpServers,
134
134
  sharedPathOfKeyId,
135
135
  shouldInstallAsset,
136
+ skillFolderOf,
136
137
  skillNames,
137
138
  stripAgentsMd,
138
139
  stripHarnessImport,
@@ -145,7 +146,7 @@ import {
145
146
  withoutAppleDouble,
146
147
  wrapHarnessBlock,
147
148
  writeInstallLog
148
- } from "./chunk-HB6UJWC6.js";
149
+ } from "./chunk-JIVFWRYB.js";
149
150
 
150
151
  // node_modules/sisteransi/src/index.js
151
152
  var require_src = __commonJS({
@@ -1948,8 +1949,8 @@ function legacyGitignoreSeed(text, render, previous) {
1948
1949
 
1949
1950
  // src/installer.ts
1950
1951
  init_esm_shims();
1951
- import { chmodSync as chmodSync3, existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync21, writeFileSync as writeFileSync7 } from "fs";
1952
- import { basename as basename6, join as join29, relative as relative7, resolve, sep as sep8 } 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";
1953
1954
 
1954
1955
  // src/anchor-seed.ts
1955
1956
  init_esm_shims();
@@ -2770,6 +2771,11 @@ function renderBundledSkill(source) {
2770
2771
  ${portBody(body)}
2771
2772
  `;
2772
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
+ }
2773
2779
  function portBody(body) {
2774
2780
  return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
2775
2781
  }
@@ -2784,7 +2790,7 @@ function writeBundledSkillDirs(params) {
2784
2790
  }
2785
2791
  const targetDir = join13(projectDir, ".agents", "skills", id);
2786
2792
  const targetSkill = join13(targetDir, "SKILL.md");
2787
- if (!writer.write(targetSkill, renderBundledSkill(readFileSync9(srcSkill, "utf8")))) {
2793
+ if (!writer.write(targetSkill, renderBundledSkillFile("SKILL.md", readFileSync9(srcSkill, "utf8")))) {
2788
2794
  continue;
2789
2795
  }
2790
2796
  written.push(targetSkill);
@@ -2793,8 +2799,7 @@ function writeBundledSkillDirs(params) {
2793
2799
  if (rel.split("/").some((segment) => segment.startsWith("."))) continue;
2794
2800
  const segments = rel.split("/");
2795
2801
  const raw = readFileSync9(join13(srcDir, ...segments), "utf8");
2796
- const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
2797
- ` : raw;
2802
+ const ported = renderBundledSkillFile(rel, raw);
2798
2803
  const target = join13(targetDir, ...segments);
2799
2804
  if (!writer.write(target, ported, { createInRefresh: true })) continue;
2800
2805
  if (rel.endsWith(".sh")) chmodSync(target, 493);
@@ -2812,6 +2817,10 @@ trigger: glob
2812
2817
  globs: "${rule.paths.join(", ")}"
2813
2818
  ---
2814
2819
 
2820
+ `;
2821
+ }
2822
+ function renderAntigravityRule(rule) {
2823
+ return `${antigravityFrontmatter(rule)}${rule.body}
2815
2824
  `;
2816
2825
  }
2817
2826
  function runAntigravityTransform(params) {
@@ -2838,8 +2847,7 @@ function runAntigravityTransform(params) {
2838
2847
  const harnessRuleFiles = [];
2839
2848
  for (const rule of portRules(harnessRoot, rules)) {
2840
2849
  const target = join14(projectDir, ".agents", "rules", `${rule.name}.md`);
2841
- const content = `${antigravityFrontmatter(rule)}${rule.body}
2842
- `;
2850
+ const content = renderAntigravityRule(rule);
2843
2851
  if (!writer.write(target, content, { createInRefresh: antigravityInstalled })) {
2844
2852
  continue;
2845
2853
  }
@@ -3090,6 +3098,9 @@ function quoteIfNeeded(name) {
3090
3098
  // src/codex/transform.ts
3091
3099
  var HOOK_NAMES = ["session-start"];
3092
3100
  var ENV_VAR_RENAME = /CLAUDE_PROJECT_DIR/g;
3101
+ function renderCodexHook(source) {
3102
+ return source.replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
3103
+ }
3093
3104
  function runCodexTransform(params) {
3094
3105
  const {
3095
3106
  harnessRoot,
@@ -3131,7 +3142,7 @@ function runCodexTransform(params) {
3131
3142
  if (!existsSync13(src)) {
3132
3143
  continue;
3133
3144
  }
3134
- const ported = readFileSync12(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
3145
+ const ported = renderCodexHook(readFileSync12(src, "utf8"));
3135
3146
  const target = join17(hookDir, `${hook}.sh`);
3136
3147
  if (!writer.write(target, ported)) continue;
3137
3148
  chmodSync2(target, 493);
@@ -3550,12 +3561,58 @@ function runCliTransforms(params) {
3550
3561
 
3551
3562
  // src/displaced.ts
3552
3563
  init_esm_shims();
3553
- import { lstatSync as lstatSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync } from "fs";
3554
- import { basename as basename3, dirname as dirname5, join as join21, posix as posix2 } 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
+ }
3555
3612
 
3556
3613
  // src/harness-slot.ts
3557
3614
  init_esm_shims();
3558
- import { isAbsolute, join as join20, posix, relative as relative4, sep as sep5 } from "path";
3615
+ import { isAbsolute, join as join21, posix as posix2, relative as relative4, sep as sep5 } from "path";
3559
3616
  var ownedOf = (...kinds) => [
3560
3617
  ...new Set(
3561
3618
  Object.values(CLI_OWNERSHIP).flat().filter((o) => kinds.includes(o.kind)).map((o) => o.path)
@@ -3577,9 +3634,9 @@ function within(rel, slots) {
3577
3634
  return slots.places.some((p2) => p2.endsWith("/") ? rel.startsWith(p2) : rel === p2);
3578
3635
  }
3579
3636
  function realRel(projectDir, norm, follow) {
3580
- const target = follow ? resolveWriteTarget(join20(projectDir, norm)) : (() => {
3581
- const dir = resolveWriteTarget(join20(projectDir, posix.dirname(norm)));
3582
- return dir === null ? null : join20(dir, posix.basename(norm));
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));
3583
3640
  })();
3584
3641
  if (target === null) return null;
3585
3642
  const rel = relative4(resolveWriteTarget(projectDir) ?? projectDir, target);
@@ -3587,29 +3644,65 @@ function realRel(projectDir, norm, follow) {
3587
3644
  const slash = rel.split(sep5).join("/");
3588
3645
  return slash === ".." || slash.startsWith("../") ? null : slash;
3589
3646
  }
3647
+ function inSlotByName(path, slots = HARNESS_FILES) {
3648
+ return within(posix2.normalize(path), slots);
3649
+ }
3590
3650
  function inHarnessSlot(projectDir, path, slots = HARNESS_FILES, opts = {}) {
3591
- const norm = posix.normalize(path);
3651
+ const norm = posix2.normalize(path);
3592
3652
  if (!within(norm, slots)) return false;
3593
3653
  const real = realRel(projectDir, norm, opts.follow === true);
3594
3654
  return real !== null && within(real, slots);
3595
3655
  }
3596
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
+
3597
3675
  // src/displaced.ts
3598
3676
  function exists(abs) {
3599
3677
  try {
3600
- lstatSync3(abs);
3678
+ lstatSync4(abs);
3601
3679
  return true;
3602
3680
  } catch {
3603
3681
  return false;
3604
3682
  }
3605
3683
  }
3606
3684
  function outsideBackup(projectDir, backup) {
3607
- const abs = join21(projectDir, backup);
3685
+ const abs = join22(projectDir, backup);
3608
3686
  const hit = outsideProjectTarget(projectDir, dirname5(abs));
3609
- return hit === null ? null : join21(hit.target, basename3(abs));
3687
+ return hit === null ? null : join22(hit.target, basename3(abs));
3610
3688
  }
3611
3689
  function isBackupOf(path, backup) {
3612
- return backupOriginal(posix2.normalize(backup)) === posix2.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
+ };
3613
3706
  }
3614
3707
  function isLegacyDisplaced(log, projectDir, f) {
3615
3708
  if (f.change !== "displaced") return false;
@@ -3617,7 +3710,7 @@ function isLegacyDisplaced(log, projectDir, f) {
3617
3710
  if (outsideBackup(projectDir, at2) !== null) return false;
3618
3711
  let original;
3619
3712
  try {
3620
- original = readFileSync15(join21(projectDir, at2), "utf8");
3713
+ original = readFileSync16(join22(projectDir, at2), "utf8");
3621
3714
  } catch {
3622
3715
  return false;
3623
3716
  }
@@ -3625,17 +3718,21 @@ function isLegacyDisplaced(log, projectDir, f) {
3625
3718
  }
3626
3719
  function restoreDisplaced(log, projectDir, opts) {
3627
3720
  const out = [];
3721
+ let bundleTargets;
3628
3722
  for (const f of log.rootFiles ?? []) {
3629
3723
  if (f.change !== "displaced" || !opts.has(f.path)) continue;
3630
- const slot = join21(projectDir, f.path);
3724
+ const slot = join22(projectDir, f.path);
3631
3725
  if (outsideProjectTarget(projectDir, slot) !== null) continue;
3632
3726
  if (!inHarnessSlot(projectDir, f.path)) {
3727
+ bundleTargets ??= recordedTrackTargets(log);
3728
+ const handBack = handBackOf(log, projectDir, f, bundleTargets);
3633
3729
  out.push({
3634
3730
  path: f.path,
3635
3731
  backup: f.notes[0] ?? null,
3636
3732
  verdict: "advise",
3637
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",
3638
- forget: true
3734
+ forget: true,
3735
+ ...handBack === null ? {} : { handBack }
3639
3736
  });
3640
3737
  continue;
3641
3738
  }
@@ -3646,7 +3743,7 @@ function restoreDisplaced(log, projectDir, opts) {
3646
3743
  path: f.path,
3647
3744
  backup,
3648
3745
  verdict: "advise",
3649
- 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`,
3650
3747
  forget: true
3651
3748
  });
3652
3749
  continue;
@@ -3656,19 +3753,19 @@ function restoreDisplaced(log, projectDir, opts) {
3656
3753
  path: f.path,
3657
3754
  backup,
3658
3755
  verdict: "advise",
3659
- 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`,
3660
3757
  forget: true
3661
3758
  });
3662
3759
  continue;
3663
3760
  }
3664
3761
  const d2 = judgeDisplaced({
3665
3762
  backup,
3666
- backupExists: backup !== null && exists(join21(projectDir, backup)),
3763
+ backupExists: backup !== null && exists(join22(projectDir, backup)),
3667
3764
  slotEmpty: !exists(slot) || !opts.write && opts.willBeGone?.has(f.path) === true
3668
3765
  });
3669
3766
  if (opts.write && d2.verdict === "restore" && backup !== null) {
3670
3767
  mkdirSync5(dirname5(slot), { recursive: true });
3671
- renameSync(join21(projectDir, backup), slot);
3768
+ renameSync(join22(projectDir, backup), slot);
3672
3769
  }
3673
3770
  out.push({
3674
3771
  path: f.path,
@@ -3688,7 +3785,7 @@ function withoutRestored(rootFiles, outcomes) {
3688
3785
  // src/excluded-still-there.ts
3689
3786
  init_esm_shims();
3690
3787
  import { existsSync as existsSync16 } from "fs";
3691
- import { join as join22 } from "path";
3788
+ import { join as join23 } from "path";
3692
3789
  function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
3693
3790
  const out = [];
3694
3791
  const add = (id, catalog) => {
@@ -3701,7 +3798,7 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
3701
3798
  for (const id of excluded) {
3702
3799
  if (INTERNAL_BUNDLED_SKILL_IDS.includes(id)) {
3703
3800
  const there = [".claude/skills", ".agents/skills"].some(
3704
- (d2) => existsSync16(join22(projectDir, d2, id))
3801
+ (d2) => existsSync16(join23(projectDir, d2, id))
3705
3802
  );
3706
3803
  if (there) add(id, false);
3707
3804
  } else {
@@ -3714,10 +3811,13 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
3714
3811
  }
3715
3812
  function assetStillThere(projectDir, asset) {
3716
3813
  if (asset.method !== "skill" || asset.scope === "global") return true;
3717
- if (asset.files !== void 0)
3718
- return asset.files.some((f) => existsSync16(join22(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;
3719
3816
  const dir = asset.detail.skill ?? asset.id;
3720
- return [".claude/skills", ".agents/skills"].some((d2) => existsSync16(join22(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
+ );
3721
3821
  }
3722
3822
 
3723
3823
  // src/external-installer.ts
@@ -3727,10 +3827,10 @@ import {
3727
3827
  accessSync,
3728
3828
  existsSync as existsSync17,
3729
3829
  constants as fsConstants,
3730
- lstatSync as lstatSync4,
3830
+ lstatSync as lstatSync5,
3731
3831
  mkdirSync as mkdirSync6,
3732
3832
  readdirSync as readdirSync3,
3733
- readFileSync as readFileSync16,
3833
+ readFileSync as readFileSync17,
3734
3834
  readlinkSync,
3735
3835
  realpathSync as realpathSync3,
3736
3836
  renameSync as renameSync2,
@@ -3738,7 +3838,10 @@ import {
3738
3838
  rmSync as rmSync3
3739
3839
  } from "fs";
3740
3840
  import { homedir as homedir2 } from "os";
3741
- import { basename as basename4, dirname as dirname6, join as join23, relative as relative5, sep as sep6 } 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
+ }
3742
3845
  var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
3743
3846
  function skillRootOf(cli) {
3744
3847
  return cli === "claude" ? ".claude/skills" : ".agents/skills";
@@ -3746,11 +3849,11 @@ function skillRootOf(cli) {
3746
3849
  function snapshotSkillRoots(projectDir) {
3747
3850
  const out = /* @__PURE__ */ new Map();
3748
3851
  for (const root of SKILL_ROOTS) {
3749
- if (outsideProjectTarget(projectDir, join23(projectDir, root)) !== null) continue;
3750
- for (const rel of listFilesRecursive(join23(projectDir, root))) {
3852
+ if (outsideProjectTarget(projectDir, join24(projectDir, root)) !== null) continue;
3853
+ for (const rel of listFilesRecursive(join24(projectDir, root))) {
3751
3854
  const path = `${root}/${rel}`;
3752
3855
  try {
3753
- out.set(path, hashContent(readFileSync16(join23(projectDir, path), "utf8")));
3856
+ out.set(path, hashContent(readFileSync17(join24(projectDir, path), "utf8")));
3754
3857
  } catch {
3755
3858
  }
3756
3859
  }
@@ -3768,8 +3871,8 @@ function homeSkillDirs(roots) {
3768
3871
  }
3769
3872
  for (const name of names) {
3770
3873
  try {
3771
- const st2 = lstatSync4(join23(root, name), { bigint: true });
3772
- if (st2.isDirectory()) out.set(join23(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}`);
3773
3876
  } catch {
3774
3877
  }
3775
3878
  }
@@ -3782,9 +3885,9 @@ function writtenSkillNames(before, after) {
3782
3885
  }
3783
3886
  function lockedSkillNames(source) {
3784
3887
  const xdg = process.env.XDG_STATE_HOME;
3785
- const path = xdg ? join23(xdg, "skills", ".skill-lock.json") : join23(homedir2(), ".agents", ".skill-lock.json");
3888
+ const path = xdg ? join24(xdg, "skills", ".skill-lock.json") : join24(homedir2(), ".agents", ".skill-lock.json");
3786
3889
  try {
3787
- const lock = JSON.parse(readFileSync16(path, "utf8"));
3890
+ const lock = JSON.parse(readFileSync17(path, "utf8"));
3788
3891
  if (typeof lock.version !== "number" || lock.version < 3) return null;
3789
3892
  if (typeof lock.skills !== "object" || lock.skills === null) return null;
3790
3893
  const out = /* @__PURE__ */ new Set();
@@ -3884,18 +3987,30 @@ function installOne(asset, ctx) {
3884
3987
  const written = writtenSkillNames(before2, homeSkillDirs(roots));
3885
3988
  return { ...result2, skills: written.filter((n) => locked.has(n)) };
3886
3989
  }
3887
- const outside = SKILL_ROOTS.flatMap((root) => {
3888
- const hit = outsideProjectTarget(cwd, join23(cwd, root));
3889
- return hit === null ? [] : [{ root, target: hit.target }];
3890
- });
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
+ );
3891
3999
  const outRoots = new Set(outside.map((o) => o.root));
3892
4000
  const cli = ctx.cli.filter((c3) => !outRoots.has(skillRootOf(c3)));
3893
4001
  if (outside.length > 0 && cli.length === 0) {
3894
- 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
+ ];
3895
4010
  return {
3896
4011
  asset,
3897
4012
  ok: false,
3898
- 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`,
3899
4014
  outside
3900
4015
  };
3901
4016
  }
@@ -3974,6 +4089,7 @@ function refreshExternalSkills(projectDir, deps = {}) {
3974
4089
  const slotCtx = { log, scope: "project" };
3975
4090
  const settle = holdSkillSlots(projectDir, {
3976
4091
  recorded: log.assets.filter((a) => targetIds.has(a.id)),
4092
+ assets: targets,
3977
4093
  slots: skillSlotsFor(inProject, clis, slotCtx),
3978
4094
  unlisted: unlistedSkillSlots(inProject, clis, slotCtx),
3979
4095
  log,
@@ -4028,6 +4144,9 @@ function refreshExternalSkills(projectDir, deps = {}) {
4028
4144
  );
4029
4145
  const late = reclaimNewlyNamed(projectDir, log, next);
4030
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
+ );
4031
4150
  return {
4032
4151
  attempted: report.attempted.length,
4033
4152
  refreshed: report.succeeded,
@@ -4035,6 +4154,7 @@ function refreshExternalSkills(projectDir, deps = {}) {
4035
4154
  notInCatalog,
4036
4155
  unknown: false,
4037
4156
  ...reclaimedAll.length > 0 ? { reclaimed: reclaimedAll } : {},
4157
+ ...leftAsIs.length > 0 ? { leftAsIs } : {},
4038
4158
  judged: [...reclaimedAll.flatMap((r) => r.judged), ...judged]
4039
4159
  };
4040
4160
  }
@@ -4042,15 +4162,16 @@ function holdSkillSlots(projectDir, hold) {
4042
4162
  const now = /* @__PURE__ */ new Date();
4043
4163
  const asides = /* @__PURE__ */ new Map();
4044
4164
  const moved = [];
4165
+ const kept = slotFilesToKeep(projectDir, hold);
4045
4166
  try {
4046
- for (const f of slotFilesToKeep(projectDir, hold)) {
4167
+ for (const f of kept.files) {
4047
4168
  const root = dirname6(f.slot);
4048
4169
  let dir = asides.get(root);
4049
4170
  if (dir === void 0) {
4050
- dir = claimBackupDir(join23(projectDir, root), now);
4171
+ dir = claimBackupDir(join24(projectDir, root), now);
4051
4172
  asides.set(root, dir);
4052
4173
  }
4053
- const aside = join23(dir, f.rel.slice(root.length + 1));
4174
+ const aside = join24(dir, f.rel.slice(root.length + 1));
4054
4175
  mkdirSync6(dirname6(aside), { recursive: true });
4055
4176
  renameSync2(f.abs, aside);
4056
4177
  moved.push({ ...f, aside });
@@ -4073,19 +4194,32 @@ function holdSkillSlots(projectDir, hold) {
4073
4194
  displaced: [],
4074
4195
  judged: []
4075
4196
  };
4197
+ const ok = (report?.attempted ?? []).filter((r) => r.ok);
4076
4198
  const rewritten = new Set(
4077
- (report?.attempted ?? []).flatMap(
4078
- (r) => r.ok ? (r.files?.created ?? []).map((x) => skillFolderOf(x.path)) : []
4079
- )
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)) ?? [])
4080
4207
  );
4081
4208
  const back = /* @__PURE__ */ new Set();
4082
4209
  const failed = [];
4083
4210
  for (const f of moved) {
4084
4211
  const restore = failedSlots === null || failedSlots.some((s) => f.rel === s || f.rel.startsWith(`${s}/`));
4085
4212
  try {
4086
- const r = settleAside(projectDir, f, restore, rewritten.has(skillFolderOf(f.rel)));
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
+ );
4087
4221
  if (r === "back") {
4088
- back.add(f.rel);
4222
+ if (literal) back.add(f.rel);
4089
4223
  continue;
4090
4224
  }
4091
4225
  out.backups.push(r.backup);
@@ -4111,9 +4245,30 @@ function holdSkillSlots(projectDir, hold) {
4111
4245
  `could not put back ${failed.length} file(s) moved aside before the skill tool ran \u2014 they stay in ${at2}: ${failed.join(", ")}`
4112
4246
  );
4113
4247
  }
4114
- 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 };
4115
4250
  };
4116
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
+ }
4117
4272
  function skillSlotsFor(assets, clis, ctx) {
4118
4273
  if (resolveScope(ctx.scope) === "global") return [];
4119
4274
  const roots = clis.length === 0 ? [...SKILL_ROOTS] : [...new Set(clis.map(skillRootOf))];
@@ -4126,11 +4281,15 @@ function skillSlotsFor(assets, clis, ctx) {
4126
4281
  return [...out];
4127
4282
  }
4128
4283
  function unlistedSkillSlots(assets, clis, ctx) {
4129
- const unlisted = assets.filter((a) => {
4284
+ const out = assets.flatMap((a) => {
4130
4285
  const prior = ctx.log?.assets.find((r) => r.id === a.id);
4131
- return prior !== void 0 && prior.files === void 0;
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));
4132
4291
  });
4133
- return skillSlotsFor(unlisted, clis, ctx);
4292
+ return [...new Set(out)];
4134
4293
  }
4135
4294
  function reclaimGlobalSkillCopies(projectDir, log, opts) {
4136
4295
  const clis = installedClis(log);
@@ -4150,13 +4309,13 @@ function reclaimGlobalSkillCopies(projectDir, log, opts) {
4150
4309
  for (const name of names) {
4151
4310
  for (const root of roots) {
4152
4311
  const slot = `${root}/${name}`;
4153
- const real = realOrNull(join23(projectDir, slot));
4312
+ const real = realOrNull(join24(projectDir, slot));
4154
4313
  if (real !== null) {
4155
4314
  if (seen.has(real)) continue;
4156
4315
  seen.add(real);
4157
4316
  }
4158
4317
  const ctx = { log, others, write: opts.write, rm, id: a.id };
4159
- const r = reclaimCopy(projectDir, slot, join23(globalSkillRoot(root), name), ctx);
4318
+ const r = reclaimCopy(projectDir, slot, join24(globalSkillRoot(root), name), ctx);
4160
4319
  if (r !== null) out.push(r);
4161
4320
  }
4162
4321
  }
@@ -4165,17 +4324,17 @@ function reclaimGlobalSkillCopies(projectDir, log, opts) {
4165
4324
  }
4166
4325
  function globalSkillRoot(root) {
4167
4326
  if (root === ".claude/skills") {
4168
- return join23(process.env.CLAUDE_CONFIG_DIR?.trim() || join23(homedir2(), ".claude"), "skills");
4327
+ return join24(process.env.CLAUDE_CONFIG_DIR?.trim() || join24(homedir2(), ".claude"), "skills");
4169
4328
  }
4170
- return join23(homedir2(), ".agents/skills");
4329
+ return join24(homedir2(), ".agents/skills");
4171
4330
  }
4172
4331
  function isSkillName(name) {
4173
4332
  return typeof name === "string" && name !== "" && name !== "." && name !== ".." && !/[\\/]/.test(name);
4174
4333
  }
4175
4334
  function reclaimCopy(projectDir, slot, home, ctx) {
4176
- const abs = join23(projectDir, slot);
4335
+ const abs = join24(projectDir, slot);
4177
4336
  try {
4178
- if (lstatSync4(abs, { throwIfNoEntry: false })?.isDirectory() !== true) return null;
4337
+ if (lstatSync5(abs, { throwIfNoEntry: false })?.isDirectory() !== true) return null;
4179
4338
  if (outsideProjectTarget(projectDir, abs) !== null) return null;
4180
4339
  if (existsSync17(home) && realpathSync3(abs) === realpathSync3(home)) return null;
4181
4340
  } catch {
@@ -4206,15 +4365,15 @@ function reclaimCopy(projectDir, slot, home, ctx) {
4206
4365
  res.left.push({ path, why: "a link \u2014 not part of the copy the tool put here" });
4207
4366
  continue;
4208
4367
  }
4209
- const file = join23(abs, rel);
4368
+ const file = join24(abs, rel);
4210
4369
  try {
4211
- const mine = readFileSync16(file);
4212
- const same = homeExists && readOrNull(join23(home, rel))?.equals(mine) === true;
4370
+ const mine = readFileSync17(file);
4371
+ const same = homeExists && readOrNull(join24(home, rel))?.equals(mine) === true;
4213
4372
  const backupAbs = !same && ctx.write ? backupFile(file, now) : void 0;
4214
4373
  if (ctx.write) ctx.rm(file);
4215
4374
  res.removed.push(path);
4216
4375
  if (same) continue;
4217
- const line = homeExists ? `not the same as the global copy (${join23(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`;
4218
4377
  const judged = {
4219
4378
  path,
4220
4379
  verdict: "backup+remove",
@@ -4233,6 +4392,14 @@ function reclaimCopy(projectDir, slot, home, ctx) {
4233
4392
  if (ctx.write && res.removed.length > 0) pruneEmptyDirs(abs);
4234
4393
  return res.removed.length > 0 || res.left.length > 0 ? res : null;
4235
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
+ }
4236
4403
  function realOrNull(abs) {
4237
4404
  try {
4238
4405
  return realpathSync3(abs);
@@ -4242,11 +4409,11 @@ function realOrNull(abs) {
4242
4409
  }
4243
4410
  function realFileOrNull(abs) {
4244
4411
  const dir = realOrNull(dirname6(abs));
4245
- return dir === null ? null : join23(dir, basename4(abs));
4412
+ return dir === null ? null : join24(dir, basename4(abs));
4246
4413
  }
4247
4414
  function readOrNull(abs) {
4248
4415
  try {
4249
- return readFileSync16(abs);
4416
+ return readFileSync17(abs);
4250
4417
  } catch {
4251
4418
  return null;
4252
4419
  }
@@ -4259,7 +4426,7 @@ function slotFilesToKeep(projectDir, hold) {
4259
4426
  const sha = new Map(mine.map((f) => [f.path, f.sha256]));
4260
4427
  const realSha = new Map(
4261
4428
  mine.flatMap((f) => {
4262
- const real = realFileOrNull(join23(projectDir, f.path));
4429
+ const real = realFileOrNull(join24(projectDir, f.path));
4263
4430
  return real === null ? [] : [[real, f.sha256]];
4264
4431
  })
4265
4432
  );
@@ -4268,42 +4435,61 @@ function slotFilesToKeep(projectDir, hold) {
4268
4435
  (hold.log?.assets ?? []).filter((a) => !targetIds.has(a.id)).flatMap((a) => (a.files ?? []).map((f) => f.path))
4269
4436
  );
4270
4437
  const slots = /* @__PURE__ */ new Set([
4271
- ...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
+ ),
4272
4445
  ...hold.slots
4273
4446
  ]);
4274
4447
  const unlisted = new Set(hold.unlisted ?? []);
4275
- const unlistedAt = (path) => {
4448
+ const ownerOf = (path) => {
4276
4449
  let owner = "";
4277
4450
  for (const s of slots) if (path.startsWith(`${s}/`) && s.length > owner.length) owner = s;
4278
- return unlisted.has(owner);
4451
+ return owner;
4279
4452
  };
4453
+ const unlistedAt = (path) => unlisted.has(ownerOf(path));
4280
4454
  const out = /* @__PURE__ */ new Map();
4455
+ const left = /* @__PURE__ */ new Set();
4281
4456
  const realProject = realpathSync3(projectDir);
4282
4457
  for (const slot of slots) {
4283
- if (outsideProjectTarget(projectDir, join23(projectDir, slot)) !== null) continue;
4284
- for (const { rel, link } of listEntriesRecursive(join23(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))) {
4285
4461
  const path = `${slot}/${rel}`;
4286
- if (link && SKILL_ROOTS.includes(dirname6(path))) continue;
4287
- const abs = join23(projectDir, path);
4288
- const real = join23(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));
4289
4468
  const prev = out.get(real);
4290
- if (prev !== void 0 && (prev.rel === path || real !== join23(realProject, path))) continue;
4469
+ if (prev !== void 0 && (prev.rel === path || real !== join24(realProject, path))) continue;
4291
4470
  const recordedSha = sha.get(path) ?? realSha.get(real);
4292
- 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));
4293
4473
  continue;
4474
+ }
4294
4475
  try {
4295
4476
  if (!link) {
4296
- if (recordedSha !== void 0 && hashContent(readFileSync16(abs, "utf8")) === recordedSha)
4477
+ if (recordedSha !== void 0 && hashContent(readFileSync17(abs, "utf8")) === recordedSha) {
4478
+ left.add(skillFolderOf(path));
4297
4479
  continue;
4480
+ }
4298
4481
  accessSync(abs, fsConstants.R_OK);
4299
4482
  }
4300
4483
  } catch {
4484
+ left.add(skillFolderOf(path));
4301
4485
  continue;
4302
4486
  }
4303
4487
  const listed = recordedSha !== void 0;
4304
4488
  out.set(real, {
4305
4489
  abs,
4490
+ real,
4306
4491
  rel: path,
4492
+ folder: realOrNull(join24(projectDir, skillFolderOf(path))),
4307
4493
  slot,
4308
4494
  recorded: listed,
4309
4495
  unlisted: !listed && unlistedAt(path) && backupOriginal(path) === null,
@@ -4311,20 +4497,51 @@ function slotFilesToKeep(projectDir, hold) {
4311
4497
  });
4312
4498
  }
4313
4499
  }
4314
- return [...out.values()];
4500
+ return { files: [...out.values()], left, ownerOf };
4501
+ }
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 });
4315
4532
  }
4316
4533
  function settleAside(projectDir, f, restore, folderRewritten) {
4317
4534
  let now = null;
4318
4535
  let vacant = false;
4319
4536
  try {
4320
- if (f.link) lstatSync4(f.abs);
4321
- else now = readFileSync16(f.abs);
4537
+ if (f.link) lstatSync5(f.abs);
4538
+ else now = readFileSync17(f.abs);
4322
4539
  } catch (e2) {
4323
4540
  const code = e2.code;
4324
4541
  if (code !== "ENOENT" && code !== "ENOTDIR") throw e2;
4325
4542
  vacant = true;
4326
4543
  }
4327
- const keep = vacant ? f.unlisted ? !folderRewritten : 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;
4328
4545
  if (restore || keep) {
4329
4546
  mkdirSync6(dirname6(f.abs), { recursive: true });
4330
4547
  renameSync2(f.aside, f.abs);
@@ -4339,25 +4556,27 @@ function settleAside(projectDir, f, restore, folderRewritten) {
4339
4556
  displaced: { path: f.rel, change: "displaced", notes: [toProjectRel(projectDir, backup)] }
4340
4557
  };
4341
4558
  }
4342
- function skillFolderOf(path) {
4343
- return path.split("/").slice(0, 3).join("/");
4344
- }
4345
- function withoutCreated(report, paths) {
4346
- if (paths.size === 0) return report;
4559
+ function settledReport(report, paths, covered) {
4560
+ if (paths.size === 0 && covered.size === 0) return report;
4347
4561
  return {
4348
4562
  ...report,
4349
- attempted: report.attempted.map(
4350
- (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 {
4351
4570
  ...r,
4352
- files: { ...r.files, created: r.files.created.filter((x) => !paths.has(x.path)) }
4353
- }
4354
- )
4571
+ files: { ...r.files, created, ...folders ? { covered: [...folders].sort() } : {} }
4572
+ };
4573
+ })
4355
4574
  };
4356
4575
  }
4357
4576
  function pruneEmptyDirs(dir) {
4358
4577
  try {
4359
4578
  for (const e2 of readdirSync3(dir, { withFileTypes: true }))
4360
- if (e2.isDirectory()) pruneEmptyDirs(join23(dir, e2.name));
4579
+ if (e2.isDirectory()) pruneEmptyDirs(join24(dir, e2.name));
4361
4580
  rmdirSync(dir);
4362
4581
  } catch {
4363
4582
  }
@@ -4367,7 +4586,7 @@ function toProjectRel(projectDir, abs) {
4367
4586
  }
4368
4587
  function recordRefreshedFiles(projectDir, log, report, displaced, writeLog) {
4369
4588
  const next = new Map(
4370
- 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])
4371
4590
  );
4372
4591
  const named = new Map(
4373
4592
  report.attempted.flatMap(
@@ -4376,13 +4595,19 @@ function recordRefreshedFiles(projectDir, log, report, displaced, writeLog) {
4376
4595
  );
4377
4596
  let changed = displaced.length > 0;
4378
4597
  const assets = log.assets.map((a) => {
4379
- 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;
4380
4602
  const { skills } = skillNames(a, a.scope, named.get(a.id));
4381
- const sameFiles = files === void 0 || JSON.stringify(files) === JSON.stringify(a.files);
4382
4603
  const sameSkills = skills === void 0 || JSON.stringify(skills) === JSON.stringify(a.skills);
4383
4604
  if (sameFiles && sameSkills) return a;
4384
4605
  changed = true;
4385
- return { ...a, ...sameFiles ? {} : { files }, ...skills !== void 0 ? { skills } : {} };
4606
+ return {
4607
+ ...a,
4608
+ ...sameFiles ? {} : { files, ...partial },
4609
+ ...skills !== void 0 ? { skills } : {}
4610
+ };
4386
4611
  });
4387
4612
  if (!changed) return log;
4388
4613
  const written = {
@@ -4490,9 +4715,9 @@ function detectVersion(method, spawn, scope, projectDir) {
4490
4715
  case "plugin":
4491
4716
  return detectPluginVersion(method.pluginId, scope, projectDir);
4492
4717
  case "npm": {
4493
- const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join23(projectDir, "node_modules", method.pkg, "package.json");
4718
+ const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join24(projectDir, "node_modules", method.pkg, "package.json");
4494
4719
  if (!pkgJson || !existsSync17(pkgJson)) return void 0;
4495
- const parsed = JSON.parse(readFileSync16(pkgJson, "utf8"));
4720
+ const parsed = JSON.parse(readFileSync17(pkgJson, "utf8"));
4496
4721
  return parsed.version;
4497
4722
  }
4498
4723
  default:
@@ -4504,9 +4729,9 @@ function detectVersion(method, spawn, scope, projectDir) {
4504
4729
  }
4505
4730
  function detectPluginVersion(pluginId, scope, projectDir) {
4506
4731
  const claudeScope = scope === "global" ? "user" : "project";
4507
- const path = join23(homedir2(), ".claude/plugins/installed_plugins.json");
4732
+ const path = join24(homedir2(), ".claude/plugins/installed_plugins.json");
4508
4733
  if (!existsSync17(path)) return void 0;
4509
- const data = JSON.parse(readFileSync16(path, "utf8"));
4734
+ const data = JSON.parse(readFileSync17(path, "utf8"));
4510
4735
  const entries = data.plugins?.[pluginId] ?? [];
4511
4736
  const matches = entries.filter(
4512
4737
  (e2) => claudeScope === "user" ? e2.scope === "user" : e2.scope === "project" && e2.projectPath === projectDir
@@ -4519,7 +4744,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
4519
4744
  }
4520
4745
  function detectGlobalNpmPackageJsonPath(pkg, spawn) {
4521
4746
  const npmRoot = getNpmGlobalRoot(spawn);
4522
- return npmRoot ? join23(npmRoot, pkg, "package.json") : void 0;
4747
+ return npmRoot ? join24(npmRoot, pkg, "package.json") : void 0;
4523
4748
  }
4524
4749
  var npmGlobalRootCache;
4525
4750
  function getNpmGlobalRoot(spawn) {
@@ -4538,8 +4763,8 @@ function getNpmGlobalRoot(spawn) {
4538
4763
 
4539
4764
  // src/key-ids.ts
4540
4765
  init_esm_shims();
4541
- import { readFileSync as readFileSync17 } from "fs";
4542
- import { join as join24 } from "path";
4766
+ import { readFileSync as readFileSync18 } from "fs";
4767
+ import { join as join25 } from "path";
4543
4768
  function renderedKeyIds(harnessRoot, input, clis) {
4544
4769
  const out = /* @__PURE__ */ new Set();
4545
4770
  const add = (path, key) => {
@@ -4562,7 +4787,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
4562
4787
  for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
4563
4788
  for (const key of gitignoreRender().keys()) add(".gitignore", key);
4564
4789
  if (clis.includes("claude")) {
4565
- const template = readFileSync17(join24(harnessRoot, "templates/settings.json"), "utf8");
4790
+ const template = readFileSync18(join25(harnessRoot, "templates/settings.json"), "utf8");
4566
4791
  for (const key of renderSettingsPortion(template, () => true).keys()) {
4567
4792
  add(".claude/settings.json", key);
4568
4793
  }
@@ -4603,7 +4828,7 @@ function withoutAccepts(harnessRoot, spec, previous) {
4603
4828
 
4604
4829
  // src/linked-skill-bodies.ts
4605
4830
  init_esm_shims();
4606
- import { join as join25 } from "path";
4831
+ import { join as join26 } from "path";
4607
4832
  function refreshLinkedSkillBodies(params) {
4608
4833
  const { harnessRoot, projectDir, ids, baseline, journal } = params;
4609
4834
  const bodyKey = (id) => `.agents/skills/${id}/SKILL.md`;
@@ -4614,7 +4839,7 @@ function refreshLinkedSkillBodies(params) {
4614
4839
  );
4615
4840
  const result = writer.result();
4616
4841
  return {
4617
- updated: ours.filter((id) => written.has(join25(projectDir, bodyKey(id)))),
4842
+ updated: ours.filter((id) => written.has(join26(projectDir, bodyKey(id)))),
4618
4843
  notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
4619
4844
  files: result.files,
4620
4845
  backupPaths: result.backupPaths,
@@ -4628,8 +4853,8 @@ function refreshLinkedSkillBodies(params) {
4628
4853
 
4629
4854
  // src/out-of-track.ts
4630
4855
  init_esm_shims();
4631
- import { lstatSync as lstatSync5, readFileSync as readFileSync18, rmSync as rmSync4 } from "fs";
4632
- import { join as join26 } from "path";
4856
+ import { lstatSync as lstatSync6, readFileSync as readFileSync19, rmSync as rmSync4 } from "fs";
4857
+ import { join as join27 } from "path";
4633
4858
  var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
4634
4859
  var ANCHOR_RULE = "uzys-harness";
4635
4860
  function reclaimOutOfTrack(args) {
@@ -4649,17 +4874,17 @@ function reclaimOutOfTrack(args) {
4649
4874
  const name = AGENTS_RULE.exec(f.path)?.[1];
4650
4875
  if (name === void 0 || name === ANCHOR_RULE || rendered.has(name)) continue;
4651
4876
  if (isBaselineExcluded(`.claude/rules/${name}.md`, args.excluded)) continue;
4652
- const abs = join26(projectDir, f.path);
4877
+ const abs = join27(projectDir, f.path);
4653
4878
  if (args.outside.skip(abs)) continue;
4654
4879
  if (!inHarnessSlot(projectDir, f.path)) continue;
4655
- const stat = lstatSync5(abs, { throwIfNoEntry: false });
4880
+ const stat = lstatSync6(abs, { throwIfNoEntry: false });
4656
4881
  if (stat !== void 0 && !stat.isFile()) continue;
4657
4882
  if (stat !== void 0 && claudeCopy.has(`rules/${name}.md`)) {
4658
4883
  out.kept.push(f.path);
4659
4884
  out.bringBack[f.path] = tracksBringing(name);
4660
4885
  continue;
4661
4886
  }
4662
- const disk = stat === void 0 ? null : readFileSync18(abs, "utf8");
4887
+ const disk = stat === void 0 ? null : readFileSync19(abs, "utf8");
4663
4888
  const verdict = judge({
4664
4889
  op: "remove",
4665
4890
  kind: "harness",
@@ -4691,15 +4916,15 @@ function tracksBringing(rule) {
4691
4916
 
4692
4917
  // src/state.ts
4693
4918
  init_esm_shims();
4694
- import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
4695
- import { join as join27 } from "path";
4919
+ import { existsSync as existsSync18, readFileSync as readFileSync20 } from "fs";
4920
+ import { join as join28 } from "path";
4696
4921
  var META_FILE = ".claude/.installed-tracks";
4697
4922
  var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
4698
4923
  var TRACE_RULES = ["htmx", "nextjs", "data-analysis", "pyside6", "cli-development"].map(
4699
4924
  (r) => `.claude/rules/${r}.md`
4700
4925
  );
4701
4926
  function detectInstallState(projectDir) {
4702
- const hasClaudeDir = existsSync18(join27(projectDir, ".claude"));
4927
+ const hasClaudeDir = existsSync18(join28(projectDir, ".claude"));
4703
4928
  const read = readInstallLogStatus(projectDir);
4704
4929
  if (read.status === "ok") {
4705
4930
  return {
@@ -4719,10 +4944,10 @@ function tracksFromLog(tracks) {
4719
4944
  return [...new Set(tracks.filter(isTrack))].sort();
4720
4945
  }
4721
4946
  function collectTraces(projectDir) {
4722
- const at2 = (rel) => join27(projectDir, rel);
4947
+ const at2 = (rel) => join28(projectDir, rel);
4723
4948
  const text = (rel) => {
4724
4949
  try {
4725
- return readFileSync19(at2(rel), "utf8");
4950
+ return readFileSync20(at2(rel), "utf8");
4726
4951
  } catch {
4727
4952
  return null;
4728
4953
  }
@@ -4786,14 +5011,14 @@ init_esm_shims();
4786
5011
  import {
4787
5012
  copyFileSync,
4788
5013
  existsSync as existsSync19,
4789
- lstatSync as lstatSync6,
5014
+ lstatSync as lstatSync7,
4790
5015
  readdirSync as readdirSync4,
4791
- readFileSync as readFileSync20,
5016
+ readFileSync as readFileSync21,
4792
5017
  rmdirSync as rmdirSync2,
4793
5018
  unlinkSync as unlinkSync2,
4794
5019
  writeFileSync as writeFileSync6
4795
5020
  } from "fs";
4796
- import { basename as basename5, dirname as dirname7, join as join28, relative as relative6, sep as sep7 } from "path";
5021
+ import { basename as basename5, dirname as dirname7, join as join29, relative as relative6, sep as sep7 } from "path";
4797
5022
  var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
4798
5023
  function buildUpdateSpec(projectDir, tracks, only) {
4799
5024
  const log = readInstallLog(projectDir);
@@ -4815,7 +5040,7 @@ function parseUpdateOnly(raw) {
4815
5040
  return { ok: true, groups: [...new Set(raw)] };
4816
5041
  }
4817
5042
  function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
4818
- const claudeDir = join28(projectDir, ".claude");
5043
+ const claudeDir = join29(projectDir, ".claude");
4819
5044
  const startedAt = Date.now();
4820
5045
  const backupsAtStart = new Set(
4821
5046
  collectRunBackups(projectDir, Number.NEGATIVE_INFINITY).map((b2) => b2.backup)
@@ -4886,7 +5111,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
4886
5111
  harnessMcp,
4887
5112
  harnessWrote: harness.wrote
4888
5113
  });
4889
- const settingsPath = join28(claudeDir, "settings.json");
5114
+ const settingsPath = join29(claudeDir, "settings.json");
4890
5115
  if (claudeManaged && wants("hooks") && existsSync19(settingsPath) && !outside.skip(settingsPath)) {
4891
5116
  report.retiredHookRefsRemoved = removeRetiredHookRefs(
4892
5117
  settingsPath,
@@ -5000,6 +5225,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
5000
5225
  const skillRefresh = wants("external") ? (deps.refreshSkills ?? refreshExternalSkills)(projectDir) : { attempted: 0, refreshed: 0, failed: [], notInCatalog: [], unknown: false };
5001
5226
  report.externalSkillsRefreshed = skillRefresh.refreshed;
5002
5227
  report.externalSkillsFailed = skillRefresh.failed;
5228
+ if (skillRefresh.leftAsIs !== void 0) report.externalSkillsLeftAsIs = skillRefresh.leftAsIs;
5003
5229
  const removedOnRecord = wants("external") ? catalogRemovedOnRecord(logAtStart) : [];
5004
5230
  if (removedOnRecord.length > 0) report.catalogRemoved = removedOnRecord;
5005
5231
  const saidAsRemoved = new Set(removedOnRecord.map((e2) => e2.id));
@@ -5033,7 +5259,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
5033
5259
  judged.filter((j4) => j4.recorded === "no-sha" && j4.backup !== void 0).map((j4) => j4.path)
5034
5260
  );
5035
5261
  report.backups = collectRunBackups(projectDir, startedAt, backupsAtStart).map(
5036
- (b2) => existsSync19(join28(projectDir, b2.path)) ? b2 : { ...b2, removed: true }
5262
+ (b2) => existsSync19(join29(projectDir, b2.path)) ? b2 : { ...b2, removed: true }
5037
5263
  );
5038
5264
  const noChecksum = report.backups.map((b2) => b2.path).filter((p2) => unmeasured.has(p2));
5039
5265
  if (noChecksum.length > 0) report.noChecksum = noChecksum;
@@ -5116,7 +5342,7 @@ function reclaimOutOfTrackRecorded(projectDir, outside) {
5116
5342
  function deadMcpLeft(projectDir) {
5117
5343
  let servers;
5118
5344
  try {
5119
- servers = JSON.parse(readFileSync20(join28(projectDir, ".mcp.json"), "utf8")).mcpServers;
5345
+ servers = JSON.parse(readFileSync21(join29(projectDir, ".mcp.json"), "utf8")).mcpServers;
5120
5346
  } catch {
5121
5347
  return [];
5122
5348
  }
@@ -5131,7 +5357,7 @@ function retiredFeaturesPart(projectDir, f) {
5131
5357
  }
5132
5358
  function legacyHookOnDisk(projectDir, rel) {
5133
5359
  try {
5134
- return hasLegacyHarnessHook(readFileSync20(join28(projectDir, rel), "utf8"));
5360
+ return hasLegacyHarnessHook(readFileSync21(join29(projectDir, rel), "utf8"));
5135
5361
  } catch {
5136
5362
  return false;
5137
5363
  }
@@ -5157,13 +5383,13 @@ function writeUpdateSharedFiles(args) {
5157
5383
  excluded: adapterExcluded(harnessRoot, excluded)
5158
5384
  });
5159
5385
  const tracks = installedTracks(projectDir);
5160
- const settingsSource = join28(templatesDir, "settings.json");
5386
+ const settingsSource = join29(templatesDir, "settings.json");
5161
5387
  if (args.claudeManaged && wants("hooks") && existsSync19(settingsSource)) {
5162
5388
  const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
5163
5389
  const manifest = buildManifest(spec);
5164
5390
  writeSettingsShared(
5165
5391
  writer,
5166
- readFileSync20(settingsSource, "utf8"),
5392
+ readFileSync21(settingsSource, "utf8"),
5167
5393
  projectDir,
5168
5394
  log,
5169
5395
  (script) => {
@@ -5232,8 +5458,8 @@ function catalogRemovedOnRecord(log) {
5232
5458
  }
5233
5459
  function staleSkillDirs(projectDir, claudeDir, clis) {
5234
5460
  const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
5235
- const dirs = [join28(claudeDir, "skills")];
5236
- if (clis.some((cli) => cli !== "claude")) dirs.push(join28(projectDir, ".agents", "skills"));
5461
+ const dirs = [join29(claudeDir, "skills")];
5462
+ if (clis.some((cli) => cli !== "claude")) dirs.push(join29(projectDir, ".agents", "skills"));
5237
5463
  const ids = /* @__PURE__ */ new Set();
5238
5464
  for (const dir of dirs) {
5239
5465
  if (!existsSync19(dir)) continue;
@@ -5244,13 +5470,13 @@ function staleSkillDirs(projectDir, claudeDir, clis) {
5244
5470
  return [...ids];
5245
5471
  }
5246
5472
  function staleAgentFiles(claudeDir) {
5247
- const dir = join28(claudeDir, "agents");
5473
+ const dir = join29(claudeDir, "agents");
5248
5474
  if (!existsSync19(dir)) return [];
5249
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));
5250
5476
  }
5251
5477
  function demotedAgentFiles(claudeDir, tracks) {
5252
5478
  if (tracks.length === 0) return [];
5253
- const dir = join28(claudeDir, "agents");
5479
+ const dir = join29(claudeDir, "agents");
5254
5480
  if (!existsSync19(dir)) return [];
5255
5481
  const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
5256
5482
  ([id]) => id
@@ -5266,19 +5492,19 @@ function updateHarnessTargets(projectDir, templatesDir, log, wants, report, fore
5266
5492
  if (!wants(e2.target.startsWith(".claude/hooks/") ? "hooks" : "rules")) continue;
5267
5493
  if (e2.target.startsWith(".claude/") && !claude) continue;
5268
5494
  if (isBaselineExcluded(e2.target, excluded)) continue;
5269
- const source = join28(templatesDir, e2.source);
5495
+ const source = join29(templatesDir, e2.source);
5270
5496
  if (!existsSync19(source)) continue;
5271
5497
  const policy = POLICY_DIRS.find((d2) => e2.target.startsWith(`.claude/${d2.dir}/`));
5272
5498
  files.push({
5273
5499
  path: e2.target,
5274
5500
  source,
5275
5501
  unit: e2.target,
5276
- unitExisted: existsSync19(join28(projectDir, e2.target)),
5502
+ unitExisted: existsSync19(join29(projectDir, e2.target)),
5277
5503
  label: policy === void 0 ? e2.target : `.claude/${policy.dir}`
5278
5504
  });
5279
5505
  }
5280
5506
  const refreshedSkills = [];
5281
- const skillsSource = join28(templatesDir, "skills");
5507
+ const skillsSource = join29(templatesDir, "skills");
5282
5508
  if (!claude || !(wants("skills") || wants("new-skills")) || !existsSync19(skillsSource)) {
5283
5509
  return { files, refreshedSkills };
5284
5510
  }
@@ -5288,20 +5514,20 @@ function updateHarnessTargets(projectDir, templatesDir, log, wants, report, fore
5288
5514
  for (const d2 of readdirSync4(skillsSource, { withFileTypes: true })) {
5289
5515
  if (!d2.isDirectory()) continue;
5290
5516
  const unit = `.claude/skills/${d2.name}`;
5291
- if (occupiedByNonDirectory(join28(projectDir, unit))) {
5517
+ if (occupiedByNonDirectory(join29(projectDir, unit))) {
5292
5518
  report.skillsSkippedLinks.push(d2.name);
5293
5519
  continue;
5294
5520
  }
5295
5521
  if (!ids.has(d2.name) || excludedSkill(d2.name)) continue;
5296
- const existed = existsSync19(join28(projectDir, unit));
5522
+ const existed = existsSync19(join29(projectDir, unit));
5297
5523
  if (existed ? !wants("skills") : !wants("new-skills") || !creatable.has(d2.name)) continue;
5298
- for (const rel of listFilesRecursive(join28(skillsSource, d2.name))) {
5524
+ for (const rel of listFilesRecursive(join29(skillsSource, d2.name))) {
5299
5525
  const foreign = foreignOwnedTarget(projectDir, `${unit}/${rel}`);
5300
5526
  if (foreign !== null) {
5301
5527
  if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
5302
5528
  continue;
5303
5529
  }
5304
- const source = join28(skillsSource, d2.name, rel);
5530
+ const source = join29(skillsSource, d2.name, rel);
5305
5531
  files.push({
5306
5532
  path: `${unit}/${rel}`,
5307
5533
  source,
@@ -5339,8 +5565,8 @@ function updateHarnessFiles(args) {
5339
5565
  report.updated[label] = 0;
5340
5566
  const ctx = { prefix: dir, outside, recordOf, projectDir, judged };
5341
5567
  report.pruned[label] = pruneOrphans(
5342
- join28(projectDir, label),
5343
- join28(templatesDir, dir),
5568
+ join29(projectDir, label),
5569
+ join29(templatesDir, dir),
5344
5570
  ext,
5345
5571
  ctx,
5346
5572
  now
@@ -5424,19 +5650,19 @@ function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now,
5424
5650
  const backedUp = [];
5425
5651
  const failed = [];
5426
5652
  for (const id of ids) {
5427
- const targetSkill = join28(projectDir, ".claude/skills", id);
5428
- const bundled = new Set(listFilesRecursive(join28(templatesDir, "skills", id)));
5653
+ const targetSkill = join29(projectDir, ".claude/skills", id);
5654
+ const bundled = new Set(listFilesRecursive(join29(templatesDir, "skills", id)));
5429
5655
  for (const rel of listFilesRecursive(targetSkill)) {
5430
5656
  if (bundled.has(rel) || basename5(rel).includes(".backup-")) continue;
5431
5657
  if (foreignOwnedTarget(projectDir, `.claude/skills/${id}/${rel}`) !== null) continue;
5432
- const targetFile = join28(targetSkill, rel);
5658
+ const targetFile = join29(targetSkill, rel);
5433
5659
  if (outside.skip(targetFile)) continue;
5434
5660
  const path = `.claude/skills/${id}/${rel}`;
5435
5661
  const rec = recordOf(path);
5436
5662
  let line;
5437
5663
  let backupAbs = null;
5438
5664
  try {
5439
- const disk = rec.state === "sha" ? readFileSync20(targetFile, "utf8") : "";
5665
+ const disk = rec.state === "sha" ? readFileSync21(targetFile, "utf8") : "";
5440
5666
  line = rec.state === "none" ? NOT_RECORDED_REMOVED : judge({ op: "remove", kind: "harness", rec, disk, next: null }).line;
5441
5667
  if (rec.state !== "sha" || hashContent(disk) !== rec.sha256) {
5442
5668
  backupAbs = backupFile(targetFile, now);
@@ -5504,20 +5730,20 @@ function collectRunBackups(projectDir, startedAt, exclude) {
5504
5730
  const consider = (rel) => {
5505
5731
  if (!BACKUP_SUFFIX.test(rel)) return;
5506
5732
  if (exclude?.has(rel)) return;
5507
- const abs = join28(projectDir, rel);
5508
- if (lstatSync6(abs).mtimeMs < startedAt - 1e3) return;
5733
+ const abs = join29(projectDir, rel);
5734
+ if (lstatSync7(abs).mtimeMs < startedAt - 1e3) return;
5509
5735
  out.push({ path: rel.replace(BACKUP_SUFFIX, ""), backup: rel });
5510
5736
  };
5511
5737
  for (const e2 of withoutAppleDouble(readdirSync4(projectDir, { withFileTypes: true })))
5512
5738
  if (e2.isFile()) consider(e2.name);
5513
5739
  for (const dir of BACKUP_SCAN_DIRS) {
5514
- for (const { rel } of listEntriesRecursive(join28(projectDir, dir))) consider(`${dir}/${rel}`);
5740
+ for (const { rel } of listEntriesRecursive(join29(projectDir, dir))) consider(`${dir}/${rel}`);
5515
5741
  }
5516
5742
  return out.sort((a, b2) => a.path.localeCompare(b2.path));
5517
5743
  }
5518
5744
  function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new Set()) {
5519
- const dir = join28(projectDir, ".uzys-agent-harness");
5520
- const file = join28(dir, "update-backups.json");
5745
+ const dir = join29(projectDir, ".uzys-agent-harness");
5746
+ const file = join29(dir, "update-backups.json");
5521
5747
  try {
5522
5748
  if (backups.length === 0) {
5523
5749
  if (existsSync19(file)) unlinkSync2(file);
@@ -5549,17 +5775,17 @@ function trackOnlyFileAssets(tracks) {
5549
5775
  );
5550
5776
  }
5551
5777
  function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, now = /* @__PURE__ */ new Date()) {
5552
- if (existsSync19(join28(projectDir, LEGACY_ANCHOR_FILE2))) {
5778
+ if (existsSync19(join29(projectDir, LEGACY_ANCHOR_FILE2))) {
5553
5779
  report.legacyAnchor = LEGACY_ANCHOR_FILE2;
5554
5780
  }
5555
- const templateMd = join28(templatesDir, "CLAUDE.md");
5781
+ const templateMd = join29(templatesDir, "CLAUDE.md");
5556
5782
  if (!existsSync19(templateMd)) return;
5557
- const anchor = join28(projectDir, HARNESS_ANCHOR_FILE);
5783
+ const anchor = join29(projectDir, HARNESS_ANCHOR_FILE);
5558
5784
  if (outside.skip(anchor)) return;
5559
5785
  const existed = existsSync19(anchor);
5560
5786
  if (existed) {
5561
- const current = readFileSync20(anchor, "utf8");
5562
- if (current !== readFileSync20(templateMd, "utf8")) {
5787
+ const current = readFileSync21(anchor, "utf8");
5788
+ if (current !== readFileSync21(templateMd, "utf8")) {
5563
5789
  const recorded2 = readInstallLog(projectDir)?.templates.rootClaudeMd;
5564
5790
  const owned = recorded2?.path === HARNESS_ANCHOR_FILE && hashContent(current) === recorded2.sha256;
5565
5791
  if (!owned) {
@@ -5567,7 +5793,7 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, no
5567
5793
  report.anchorBackedUp = true;
5568
5794
  const rel = relative6(projectDir, backup).split(sep7).join("/");
5569
5795
  const rec = recorded2?.path === HARNESS_ANCHOR_FILE ? { state: "sha", sha256: recorded2.sha256 } : { state: "no-sha" };
5570
- const next = readFileSync20(templateMd, "utf8");
5796
+ const next = readFileSync21(templateMd, "utf8");
5571
5797
  const { line } = judge({ op: "write", kind: "harness", rec, disk: current, next });
5572
5798
  judged.push({
5573
5799
  path: HARNESS_ANCHOR_FILE,
@@ -5592,15 +5818,15 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, no
5592
5818
  recordAnchorBaseline(projectDir, anchor);
5593
5819
  }
5594
5820
  function upsertRootImport(projectDir, outside) {
5595
- const target = join28(projectDir, "CLAUDE.md");
5821
+ const target = join29(projectDir, "CLAUDE.md");
5596
5822
  if (outside.skip(target)) return false;
5597
- const existing = existsSync19(target) ? readFileSync20(target, "utf8") : null;
5823
+ const existing = existsSync19(target) ? readFileSync21(target, "utf8") : null;
5598
5824
  const next = upsertHarnessImport(existing, {
5599
5825
  projectName: basename5(projectDir),
5600
5826
  // ADR-085 — update 는 선택 목록을 인자로 받지 않는다. 앞 단계(하네스 파일 쓰기)가 지나간 디스크로 판정한다:
5601
5827
  // `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
5602
5828
  continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
5603
- (id) => existsSync19(join28(projectDir, ".claude/skills", id))
5829
+ (id) => existsSync19(join29(projectDir, ".claude/skills", id))
5604
5830
  )
5605
5831
  });
5606
5832
  if (next === existing) return false;
@@ -5621,7 +5847,7 @@ function recordAnchorBaseline(projectDir, anchor) {
5621
5847
  ...log.templates,
5622
5848
  rootClaudeMd: {
5623
5849
  path: HARNESS_ANCHOR_FILE,
5624
- sha256: hashContent(readFileSync20(anchor, "utf8"))
5850
+ sha256: hashContent(readFileSync21(anchor, "utf8"))
5625
5851
  }
5626
5852
  }
5627
5853
  };
@@ -5635,7 +5861,7 @@ function excludedBaselineOnDisk(projectDir, log) {
5635
5861
  if (excluded.size === 0) return [];
5636
5862
  const spec = buildAssetSpec({ tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS });
5637
5863
  return buildManifest(spec).filter(
5638
- (e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync19(join28(projectDir, e2.target))
5864
+ (e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync19(join29(projectDir, e2.target))
5639
5865
  ).map((e2) => e2.target);
5640
5866
  }
5641
5867
  function excludedSkillOf(log) {
@@ -5663,7 +5889,7 @@ function recordedRenderInputs(projectDir, log, tracks = installedTracks(projectD
5663
5889
  function recordedBundledSkills(projectDir, log) {
5664
5890
  if (log === null) {
5665
5891
  return INTERNAL_BUNDLED_SKILL_IDS.filter(
5666
- (id) => existsSync19(join28(projectDir, ".claude/skills", id)) || existsSync19(join28(projectDir, ".agents/skills", id))
5892
+ (id) => existsSync19(join29(projectDir, ".claude/skills", id)) || existsSync19(join29(projectDir, ".agents/skills", id))
5667
5893
  );
5668
5894
  }
5669
5895
  const ids = recordedSkillIds(projectDir, log);
@@ -5714,9 +5940,9 @@ var SHARED_SKILLS_DIR = ".agents/skills";
5714
5940
  var SHARED_SKILL_FILE = /^\.agents\/skills\/([^/]+)\//;
5715
5941
  var SHARED_SKILL_MD = /^\.agents\/skills\/([^/]+)\/SKILL\.md$/;
5716
5942
  function sharedSkillIdsOnDisk(projectDir) {
5717
- const root = join28(projectDir, SHARED_SKILLS_DIR);
5943
+ const root = join29(projectDir, SHARED_SKILLS_DIR);
5718
5944
  if (!existsSync19(root)) return /* @__PURE__ */ new Set();
5719
- return new Set(readdirSync4(root).filter((id) => existsSync19(join28(root, id, "SKILL.md"))));
5945
+ return new Set(readdirSync4(root).filter((id) => existsSync19(join29(root, id, "SKILL.md"))));
5720
5946
  }
5721
5947
  var CREATED_SHARED_HOW = {
5722
5948
  "opencode.json": "created from template (harness part + template defaults)",
@@ -5743,14 +5969,14 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp, li
5743
5969
  const log = readInstallLog(projectDir);
5744
5970
  const presentBefore = sharedSkillIdsOnDisk(projectDir);
5745
5971
  const rulesBefore = /* @__PURE__ */ new Set();
5746
- const rulesDir = join28(projectDir, ".agents", "rules");
5972
+ const rulesDir = join29(projectDir, ".agents", "rules");
5747
5973
  if (existsSync19(rulesDir)) {
5748
5974
  try {
5749
5975
  for (const f of readdirSync4(rulesDir)) rulesBefore.add(f);
5750
5976
  } catch {
5751
5977
  }
5752
5978
  }
5753
- const agentsMdBefore = existsSync19(join28(projectDir, "AGENTS.md"));
5979
+ const agentsMdBefore = existsSync19(join29(projectDir, "AGENTS.md"));
5754
5980
  const result = runCliTransforms({
5755
5981
  harnessRoot,
5756
5982
  projectDir,
@@ -5927,12 +6153,12 @@ function pruneOrphans(target, source, ext, ctx, now) {
5927
6153
  if (!existsSync19(target) || !existsSync19(source)) return [];
5928
6154
  const removed = [];
5929
6155
  for (const file of readdirSync4(target)) {
5930
- if (!file.endsWith(ext) || existsSync19(join28(source, file))) continue;
5931
- const targetFile = join28(target, file);
6156
+ if (!file.endsWith(ext) || existsSync19(join29(source, file))) continue;
6157
+ const targetFile = join29(target, file);
5932
6158
  const path = `.claude/${ctx.prefix}/${file}`;
5933
6159
  const rec = ctx.recordOf(path);
5934
6160
  try {
5935
- const disk = readFileSync20(targetFile, "utf8");
6161
+ const disk = readFileSync21(targetFile, "utf8");
5936
6162
  const { verdict, line } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
5937
6163
  if (verdict === "leave" || ctx.outside.skip(targetFile)) continue;
5938
6164
  if (!inHarnessSlot(ctx.projectDir, path, HARNESS_FILES, { follow: true })) continue;
@@ -5954,7 +6180,7 @@ function pruneOrphans(target, source, ext, ctx, now) {
5954
6180
  return removed;
5955
6181
  }
5956
6182
  function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
5957
- const target = join28(projectDir, ".mcp-allowlist");
6183
+ const target = join29(projectDir, ".mcp-allowlist");
5958
6184
  if (!existsSync19(target)) return null;
5959
6185
  try {
5960
6186
  const backup = backupFile(target, now);
@@ -5976,11 +6202,11 @@ function recordClaudeCommand(log, tracks) {
5976
6202
  function runInstall(ctx) {
5977
6203
  const { harnessRoot, projectDir } = ctx;
5978
6204
  const mode = ctx.mode ?? "fresh";
5979
- const templatesDir = join29(harnessRoot, "templates");
6205
+ const templatesDir = join30(harnessRoot, "templates");
5980
6206
  if (!existsSync20(templatesDir)) {
5981
6207
  throw new Error(`Templates dir not found: ${templatesDir}`);
5982
6208
  }
5983
- const claudeDir = join29(projectDir, ".claude");
6209
+ const claudeDir = join30(projectDir, ".claude");
5984
6210
  const previousLog = readInstallLog(projectDir);
5985
6211
  const legacyAnchor = mode === "update" ? null : legacyAnchorTrace(projectDir, ctx.spec);
5986
6212
  if (mode === "update" && previousLog === null) {
@@ -6005,10 +6231,10 @@ function runInstall(ctx) {
6005
6231
  const manifestSpec = buildManifestSpec(spec);
6006
6232
  const baselineExcluded = new Set(spec.baselineExclude ?? []);
6007
6233
  if (spec.cli.includes("claude")) {
6008
- const settingsPath = join29(projectDir, ".claude", "settings.json");
6234
+ const settingsPath = join30(projectDir, ".claude", "settings.json");
6009
6235
  if (existsSync20(settingsPath)) {
6010
6236
  try {
6011
- JSON.parse(readFileSync21(settingsPath, "utf8"));
6237
+ JSON.parse(readFileSync22(settingsPath, "utf8"));
6012
6238
  } catch {
6013
6239
  throw new Error(
6014
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`
@@ -6305,7 +6531,7 @@ function releasedThisRun(args) {
6305
6531
  }
6306
6532
  if (id.startsWith(BASELINE_PREFIX)) {
6307
6533
  const again2 = manifest.some(
6308
- (e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync20(join29(projectDir, e2.target))
6534
+ (e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync20(join30(projectDir, e2.target))
6309
6535
  );
6310
6536
  return { id, again: again2, tail: "the harness manages it again" };
6311
6537
  }
@@ -6314,7 +6540,7 @@ function releasedThisRun(args) {
6314
6540
  options: spec.options,
6315
6541
  ...spec.userOverride ? { userOverride: spec.userOverride } : {}
6316
6542
  });
6317
- const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync20(join29(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)));
6318
6544
  return {
6319
6545
  id,
6320
6546
  again,
@@ -6326,10 +6552,10 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
6326
6552
  const done = new Set(touched);
6327
6553
  return keyExclude.flatMap((id) => {
6328
6554
  const path = sharedPathOfKeyId(id);
6329
- if (path === null || done.has(path) || !existsSync20(join29(projectDir, path))) return [];
6555
+ if (path === null || done.has(path) || !existsSync20(join30(projectDir, path))) return [];
6330
6556
  const name = adapterFor(path);
6331
6557
  const keys = [...excludedKeys(path, [id])];
6332
- const present = name === null ? null : ADAPTERS[name].read(readFileSync21(join29(projectDir, path), "utf8"), keys, projectDir);
6558
+ const present = name === null ? null : ADAPTERS[name].read(readFileSync22(join30(projectDir, path), "utf8"), keys, projectDir);
6333
6559
  return present !== null && present.size === 0 ? [] : [{ id, path }];
6334
6560
  });
6335
6561
  }
@@ -6373,7 +6599,7 @@ function installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, w
6373
6599
  continue;
6374
6600
  }
6375
6601
  if (!isCliNeutralTarget(entry.target)) continue;
6376
- const source = join29(templatesDir, entry.source);
6602
+ const source = join30(templatesDir, entry.source);
6377
6603
  if (!existsSync20(source)) {
6378
6604
  result.skipped += 1;
6379
6605
  continue;
@@ -6394,13 +6620,13 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6394
6620
  if (!entry.applies(manifestSpec)) {
6395
6621
  continue;
6396
6622
  }
6397
- const target = join29(projectDir, entry.target);
6623
+ const target = join30(projectDir, entry.target);
6398
6624
  if (isBaselineExcluded(entry.target, baselineExcluded)) {
6399
6625
  result.excluded.push(entry.target);
6400
6626
  if (existsSync20(target)) result.excludedOnDisk.push(entry.target);
6401
6627
  continue;
6402
6628
  }
6403
- const source = join29(templatesDir, entry.source);
6629
+ const source = join30(templatesDir, entry.source);
6404
6630
  if (!existsSync20(source)) {
6405
6631
  result.skipped += 1;
6406
6632
  continue;
@@ -6431,18 +6657,18 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6431
6657
  if (!result.foreignOwned.includes(slot)) result.foreignOwned.push(slot);
6432
6658
  continue;
6433
6659
  }
6434
- if (writer.skipOutside(join29(projectDir, entry.target, rel))) {
6660
+ if (writer.skipOutside(join30(projectDir, entry.target, rel))) {
6435
6661
  leftOutside += 1;
6436
6662
  continue;
6437
6663
  }
6438
- writer.harness(`${entry.target}/${rel}`, { source: join29(source, rel) });
6664
+ writer.harness(`${entry.target}/${rel}`, { source: join30(source, rel) });
6439
6665
  }
6440
6666
  if (leftOutside > 0) continue;
6441
6667
  result.dirsCopied += 1;
6442
6668
  }
6443
6669
  accumulateCategory(result.categories, entry);
6444
6670
  }
6445
- const hookDir = join29(projectDir, ".claude/hooks");
6671
+ const hookDir = join30(projectDir, ".claude/hooks");
6446
6672
  if (existsSync20(hookDir)) {
6447
6673
  chmodHooksSync(hookDir, projectDir);
6448
6674
  }
@@ -6464,8 +6690,8 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6464
6690
  });
6465
6691
  result.keptHookRefs.push(
6466
6692
  ...findStaleHookRefs(
6467
- join29(projectDir, ".claude", "settings.json"),
6468
- join29(projectDir, ".claude")
6693
+ join30(projectDir, ".claude", "settings.json"),
6694
+ join30(projectDir, ".claude")
6469
6695
  )
6470
6696
  );
6471
6697
  }
@@ -6474,7 +6700,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6474
6700
  // ADR-085 · update(`upsertRootImport`)와 같은 판정 — 실제로 깔린 스킬만 안내한다. 밖 링크라 쓰지 않은
6475
6701
  // 스킬(ADR-098)을 "installed" 라 적으면 다음 update 가 그 절을 걷어 내 두 동작이 서로 반대로 쓴다.
6476
6702
  (manifestSpec.selectedInternalSkills ?? []).filter(
6477
- (id) => existsSync20(join29(projectDir, ".claude", "skills", id))
6703
+ (id) => existsSync20(join30(projectDir, ".claude", "skills", id))
6478
6704
  ),
6479
6705
  harnessRoot,
6480
6706
  writer
@@ -6490,7 +6716,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
6490
6716
  function writeSettingsPortion(writer, source, projectDir, previousLog, hookInstalled) {
6491
6717
  return writeSettingsShared(
6492
6718
  writer,
6493
- readFileSync21(source, "utf8"),
6719
+ readFileSync22(source, "utf8"),
6494
6720
  projectDir,
6495
6721
  previousLog,
6496
6722
  hookInstalled
@@ -6543,6 +6769,7 @@ function runExternalPhase(ctx, previousLog) {
6543
6769
  const slotCtx = { log: previousLog, scope: spec.scope };
6544
6770
  const settle = holdSkillSlots(projectDir, {
6545
6771
  recorded: (previousLog?.assets ?? []).filter((a) => a.method === "skill" && skillIds.has(a.id)),
6772
+ assets: skills,
6546
6773
  slots: skillSlotsFor(skills, spec.cli, slotCtx),
6547
6774
  // #902 — 옛 판이 파일 목록 없이 깐 자산의 자리 — 그 안 파일은 되살리지 않고 백업만 남긴다
6548
6775
  unlisted: unlistedSkillSlots(skills, spec.cli, slotCtx),
@@ -6564,7 +6791,7 @@ function runExternalPhase(ctx, previousLog) {
6564
6791
  throw e2;
6565
6792
  }
6566
6793
  const { report: external, backups, displaced } = settle(ran);
6567
- const displacedBackups = new Set(displaced.map((f) => join29(projectDir, f.notes[0] ?? "")));
6794
+ const displacedBackups = new Set(displaced.map((f) => join30(projectDir, f.notes[0] ?? "")));
6568
6795
  const editBackups = backups.filter((b2) => !displacedBackups.has(b2));
6569
6796
  ctx.onProgress?.({
6570
6797
  type: "external-complete",
@@ -6777,9 +7004,9 @@ function accumulateCategory(cats, entry) {
6777
7004
  }
6778
7005
  }
6779
7006
  function writeRootClaudeMd(projectDir, continuousSkills, harnessRoot, writer) {
6780
- const target = join29(projectDir, "CLAUDE.md");
7007
+ const target = join30(projectDir, "CLAUDE.md");
6781
7008
  if (writer.skipOutside(target)) return null;
6782
- const existing = existsSync20(target) ? readFileSync21(target, "utf-8") : null;
7009
+ const existing = existsSync20(target) ? readFileSync22(target, "utf-8") : null;
6783
7010
  const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
6784
7011
  const content = upsertHarnessImport(existing, {
6785
7012
  projectName: basename6(projectDir),
@@ -6811,8 +7038,8 @@ function listHookFiles(hookDir) {
6811
7038
 
6812
7039
  // src/commands/install-render.ts
6813
7040
  init_esm_shims();
6814
- import { existsSync as existsSync22, readdirSync as readdirSync6, readFileSync as readFileSync23 } from "fs";
6815
- import { join as join31, relative as relative8 } 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";
6816
7043
 
6817
7044
  // src/preset-recommend.ts
6818
7045
  init_esm_shims();
@@ -6867,10 +7094,10 @@ function groupAssetsByCategory(assetIds) {
6867
7094
 
6868
7095
  // src/resident-entries.ts
6869
7096
  init_esm_shims();
6870
- import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
6871
- import { basename as basename7, join as join30 } from "path";
7097
+ import { existsSync as existsSync21, readFileSync as readFileSync23 } from "fs";
7098
+ import { basename as basename7, join as join31 } from "path";
6872
7099
  function residentEntries(spec) {
6873
- const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join30(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)));
6874
7101
  const forceExclude = (spec.userOverride?.forceExclude ?? []).filter((id) => !skillThere(id));
6875
7102
  const assetSpec = buildManifestSpec(
6876
7103
  spec.userOverride ? { ...spec, userOverride: { ...spec.userOverride, forceExclude } } : spec
@@ -6896,7 +7123,7 @@ function residentEntries(spec) {
6896
7123
  function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
6897
7124
  if (contextFiles !== void 0) return residentCost(entries, void 0, spec.cli, contextFiles);
6898
7125
  const cost = residentCost(entries, void 0, spec.cli, plannedClaudeContext(spec));
6899
- if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync21(join30(spec.projectDir, "AGENTS.md"))) {
7126
+ if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync21(join31(spec.projectDir, "AGENTS.md"))) {
6900
7127
  return makeResidentCost({
6901
7128
  ...cost,
6902
7129
  contextNote: "planned, not counting text already in AGENTS.md"
@@ -6907,8 +7134,8 @@ function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
6907
7134
  function plannedClaudeContext(spec) {
6908
7135
  if (!spec.cli.includes("claude")) return void 0;
6909
7136
  const root = resolveBundleRoot();
6910
- const rootClaude = join30(spec.projectDir, "CLAUDE.md");
6911
- 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;
6912
7139
  const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
6913
7140
  if (existing === null && seeded === null) return void 0;
6914
7141
  const manifestSpec = buildManifestSpec(spec);
@@ -6917,14 +7144,14 @@ function plannedClaudeContext(spec) {
6917
7144
  continuousSkills: manifestSpec.selectedInternalSkills ?? [],
6918
7145
  ...seeded === null ? {} : { projectContext: seeded }
6919
7146
  });
6920
- return [join30(root, "templates", "CLAUDE.md"), { text }];
7147
+ return [join31(root, "templates", "CLAUDE.md"), { text }];
6921
7148
  }
6922
7149
  function onDiskTargets(spec, assetSpec) {
6923
7150
  const out = /* @__PURE__ */ new Set();
6924
7151
  for (const e2 of buildManifest(assetSpec)) {
6925
7152
  if (!e2.applies(assetSpec)) continue;
6926
7153
  const t = classifyBaselineTarget(e2.target);
6927
- if (t !== null && existsSync21(join30(spec.projectDir, e2.target))) out.add(t.id);
7154
+ if (t !== null && existsSync21(join31(spec.projectDir, e2.target))) out.add(t.id);
6928
7155
  }
6929
7156
  return out;
6930
7157
  }
@@ -7053,7 +7280,7 @@ function createInstallRenderer(log, spec, verbose) {
7053
7280
  },
7054
7281
  onAssetResult: (result) => {
7055
7282
  const base = result.ok ? formatAssetMeta(result.asset, result.version) : result.message ?? "failed";
7056
- 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 ")}` : "";
7057
7284
  const meta = `${base}${outsideNote}`;
7058
7285
  log(` ${assetRow(result.ok ? "success" : "skip", result.asset.id, meta)}`);
7059
7286
  }
@@ -7111,23 +7338,23 @@ function residentEntriesOnDisk(spec) {
7111
7338
  const hasClaude = spec.cli.includes("claude");
7112
7339
  const surface = anchorSurface(spec.cli).scaffold;
7113
7340
  const mdFiles = (dir, kind, skip) => {
7114
- const abs = join31(spec.projectDir, dir);
7341
+ const abs = join32(spec.projectDir, dir);
7115
7342
  return existsSync22(abs) ? readdirSync6(abs, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md") && e2.name !== skip).map((e2) => ({
7116
7343
  source: `${kind}/${e2.name}`,
7117
7344
  // `target` 은 `residentCost` 의 분류 키다 — 실제 자리는 `file` 이 말한다.
7118
7345
  target: `.claude/${kind}/${e2.name}`,
7119
7346
  // 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 본문도,
7120
7347
  // 배포판에 더는 없는 파일도 실제로 상주하는 것은 이쪽이다.
7121
- file: join31(abs, e2.name)
7348
+ file: join32(abs, e2.name)
7122
7349
  })) : [];
7123
7350
  };
7124
- const rules = surface === "claude" ? mdFiles(join31(".claude", "rules"), "rules") : rulesInline(spec.cli) ? planned.filter((e2) => e2.target.startsWith(".claude/rules/")) : mdFiles(join31(".agents", "rules"), "rules", "uzys-harness.md");
7125
- const slot = hasClaude ? join31(".claude", "skills") : join31(".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");
7126
7353
  const skills = planned.filter((e2) => e2.target.startsWith(".claude/skills/")).map((e2) => ({
7127
7354
  ...e2,
7128
- file: join31(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
7355
+ file: join32(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
7129
7356
  })).filter((e2) => existsSync22(e2.file));
7130
- const agents = hasClaude ? mdFiles(join31(".claude", "agents"), "agents") : [];
7357
+ const agents = hasClaude ? mdFiles(join32(".claude", "agents"), "agents") : [];
7131
7358
  return [
7132
7359
  ...planned.filter(
7133
7360
  (e2) => !e2.target.startsWith(".claude/rules/") && !e2.target.startsWith(".claude/agents/") && !e2.target.startsWith(".claude/skills/")
@@ -7139,8 +7366,8 @@ function residentEntriesOnDisk(spec) {
7139
7366
  }
7140
7367
  function contextFilesOnDisk(spec) {
7141
7368
  const surface = anchorSurface(spec.cli).scaffold;
7142
- const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join31(".claude", "CLAUDE.md")] : ["AGENTS.md"];
7143
- return files.map((f) => join31(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));
7144
7371
  }
7145
7372
  function rulesInsideAgentsMd(spec, cost) {
7146
7373
  if (anchorSurface(spec.cli).scaffold !== "agents-md" || !rulesInline(spec.cli)) return cost;
@@ -7407,7 +7634,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7407
7634
  const alreadyTrustedGlobally = (() => {
7408
7635
  try {
7409
7636
  if (!existsSync22(configPath)) return false;
7410
- return hasTrustEntry(readFileSync23(configPath, "utf8"), spec.projectDir);
7637
+ return hasTrustEntry(readFileSync24(configPath, "utf8"), spec.projectDir);
7411
7638
  } catch {
7412
7639
  return false;
7413
7640
  }
@@ -7432,7 +7659,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7432
7659
  const remote = (report.shared ?? []).find((f) => f.path === ".mcp.json")?.remote ?? [];
7433
7660
  for (const line of remoteMcpNextLines(remote, spec.cli)) log(`${" ".repeat(16)} ${line}`);
7434
7661
  const scaffoldFiles = scaffoldFilesForCli(spec.cli).filter(
7435
- (f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join31(spec.projectDir, f))
7662
+ (f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join32(spec.projectDir, f))
7436
7663
  );
7437
7664
  if (scaffoldFiles.length > 0) {
7438
7665
  const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
@@ -7448,7 +7675,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7448
7675
  }
7449
7676
  function hasFillPrompt(path) {
7450
7677
  try {
7451
- return readFileSync23(path, "utf8").includes("<!-- FILL:");
7678
+ return readFileSync24(path, "utf8").includes("<!-- FILL:");
7452
7679
  } catch {
7453
7680
  return false;
7454
7681
  }
@@ -7749,6 +7976,7 @@ function renderPhase1Rows(log, baseline, verbose = false) {
7749
7976
  )
7750
7977
  );
7751
7978
  }
7979
+ for (const row of leftAsIsRows(baseline.updateMode.externalSkillsLeftAsIs ?? [])) log(row);
7752
7980
  if (baseline.updateMode.externalSkillsFailed.length > 0) {
7753
7981
  const failed = baseline.updateMode.externalSkillsFailed;
7754
7982
  const head = failed[0];
@@ -8082,6 +8310,11 @@ function excludedKeyParts(f) {
8082
8310
  }
8083
8311
  return parts;
8084
8312
  }
8313
+ function leftAsIsRows(items) {
8314
+ return items.map(
8315
+ ({ id, outside }) => assetRow("skip", "external skills", `${id}: ${outside.map(outsideSlotNote).join(" \xB7 ")}`)
8316
+ );
8317
+ }
8085
8318
  function catalogRemovedRows(items) {
8086
8319
  return items.map((e2) => {
8087
8320
  const parts = [
@@ -8521,8 +8754,8 @@ function registerInstallCommand(cli) {
8521
8754
 
8522
8755
  // src/commands/list.ts
8523
8756
  init_esm_shims();
8524
- import { existsSync as existsSync23, readFileSync as readFileSync24 } from "fs";
8525
- import { join as join32, 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";
8526
8759
  function listAction(options = {}, deps = {}) {
8527
8760
  const log = deps.log ?? console.log;
8528
8761
  const err = deps.err ?? console.error;
@@ -8626,8 +8859,8 @@ function formatHarnessFileRows(log, projectDir) {
8626
8859
  const rows = [` ${c.dim(tops.length > 0 ? tops.join(" ") : "(none recorded)")}`];
8627
8860
  const rootMd = log.templates.rootClaudeMd;
8628
8861
  if (rootMd) {
8629
- const path = join32(projectDir, rootMd.path);
8630
- 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;
8631
8864
  rows.push(
8632
8865
  modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
8633
8866
  );
@@ -8641,7 +8874,7 @@ var ROOT_FILE_CHANGE = {
8641
8874
  displaced: "\uBE44\uCF1C \uB460"
8642
8875
  };
8643
8876
  function formatRootFileRows(rootFiles, projectDir) {
8644
- const present = rootFiles.filter((f) => existsSync23(join32(projectDir, f.path)));
8877
+ const present = rootFiles.filter((f) => existsSync23(join33(projectDir, f.path)));
8645
8878
  const width = Math.max(0, ...present.map((f) => f.path.length));
8646
8879
  return present.map(
8647
8880
  (f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(ROOT_FILE_CHANGE[f.change])} ${c.dim(f.notes.join(" / "))}`
@@ -8662,9 +8895,9 @@ import {
8662
8895
  accessSync as accessSync2,
8663
8896
  existsSync as existsSync24,
8664
8897
  constants as fsConstants2,
8665
- lstatSync as lstatSync7,
8898
+ lstatSync as lstatSync8,
8666
8899
  readdirSync as readdirSync7,
8667
- readFileSync as readFileSync25,
8900
+ readFileSync as readFileSync26,
8668
8901
  readlinkSync as readlinkSync2,
8669
8902
  realpathSync as realpathSync4,
8670
8903
  rmdirSync as rmdirSync3,
@@ -8672,7 +8905,7 @@ import {
8672
8905
  statSync as statSync4,
8673
8906
  writeFileSync as writeFileSync8
8674
8907
  } from "fs";
8675
- import { dirname as dirname8, join as join33, relative as relative9, resolve as resolve4, sep as sep9 } from "path";
8908
+ import { dirname as dirname8, join as join34, relative as relative9, resolve as resolve4, sep as sep9 } from "path";
8676
8909
 
8677
8910
  // src/track-removal.ts
8678
8911
  init_esm_shims();
@@ -10119,7 +10352,7 @@ function hintFor(asset) {
10119
10352
  case "plugin":
10120
10353
  return "claude plugin uninstall --scope project";
10121
10354
  case "skill":
10122
- 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`;
10123
10356
  case "npm":
10124
10357
  return "npm uninstall --save-dev";
10125
10358
  case "npx-run":
@@ -10381,10 +10614,10 @@ Proceed?`, initialValue: false });
10381
10614
  // src/commands/uninstall.ts
10382
10615
  var CODEX_CONFIG = ".codex/config.toml";
10383
10616
  function keptOutsideLinkLine(o, log) {
10384
- 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`;
10385
10618
  const files = recordedUnder(log, o.link);
10386
10619
  if (files.length === 0) return `${head}; clean up harness files there by hand if intended.`;
10387
- 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 ");
10388
10621
  const more = files.length > KEPT_LINK_FILES_SHOWN ? ` +${files.length - KEPT_LINK_FILES_SHOWN} more` : "";
10389
10622
  return `${head}; harness files there on record: ${shown}${more} \u2014 clean them up by hand if intended.`;
10390
10623
  }
@@ -10535,6 +10768,7 @@ function uninstallAction(options, deps = {}) {
10535
10768
  if (d2.verdict === "restore") journal.push(`put back ${d2.path}`);
10536
10769
  }
10537
10770
  for (const line of displacedLines(onlyDisplaced, false)) log(line);
10771
+ for (const line of byHandLines([], onlyDisplaced, projectDir, installLog)) log(line);
10538
10772
  } else {
10539
10773
  const done = removeHarnessFiles(installLog, projectDir, FULL_SCOPE, {
10540
10774
  rm: rmJ,
@@ -10611,7 +10845,7 @@ function unwritablePaths(ctx) {
10611
10845
  const project = ctx.targetAssets.filter((a) => a.scope === "project");
10612
10846
  if (project.some((a) => a.method === "npm")) need.add(".");
10613
10847
  const parentOf = (path) => {
10614
- const abs = join33(projectDir, path);
10848
+ const abs = join34(projectDir, path);
10615
10849
  if (!safeExists(abs)) return;
10616
10850
  const real = safeRealpath(abs);
10617
10851
  if (real !== null && isOutsideProject(projectDir, real)) return;
@@ -10623,7 +10857,7 @@ function unwritablePaths(ctx) {
10623
10857
  const recordedShared = new Set((installLog.portions ?? []).map((p2) => p2.path));
10624
10858
  const made = new Set((installLog.externalFiles ?? []).map((f) => f.path));
10625
10859
  for (const p2 of UNINSTALL_SHARED) {
10626
- if (recordedShared.has(p2) && existsSync24(join33(projectDir, p2))) need.add(p2);
10860
+ if (recordedShared.has(p2) && existsSync24(join34(projectDir, p2))) need.add(p2);
10627
10861
  if (made.has(p2)) parentOf(p2);
10628
10862
  }
10629
10863
  for (const p2 of harnessCandidates(installLog)) parentOf(p2);
@@ -10633,7 +10867,7 @@ function unwritablePaths(ctx) {
10633
10867
  }
10634
10868
  return [...need].filter((p2) => {
10635
10869
  try {
10636
- accessSync2(join33(projectDir, p2), fsConstants2.W_OK);
10870
+ accessSync2(join34(projectDir, p2), fsConstants2.W_OK);
10637
10871
  return false;
10638
10872
  } catch (e2) {
10639
10873
  return e2.code !== "ENOENT";
@@ -10642,7 +10876,7 @@ function unwritablePaths(ctx) {
10642
10876
  }
10643
10877
  function safeExists(abs) {
10644
10878
  try {
10645
- lstatSync7(abs);
10879
+ lstatSync8(abs);
10646
10880
  return true;
10647
10881
  } catch {
10648
10882
  return false;
@@ -10700,7 +10934,8 @@ function executeReverse(plan, log, logSurvives, journal) {
10700
10934
  failedIds.push(step.assetId);
10701
10935
  failed++;
10702
10936
  }
10703
- 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}`));
10704
10939
  }
10705
10940
  for (const line of noReverseLines(plan, logSurvives || failed > 0)) log(line);
10706
10941
  return { succeeded, failed, removedIds, failedIds };
@@ -10736,15 +10971,15 @@ function settleLog(ctx, io) {
10736
10971
  return true;
10737
10972
  }
10738
10973
  } else {
10739
- const recordDir = join33(projectDir, INSTALL_LOG_DIR);
10974
+ const recordDir = join34(projectDir, INSTALL_LOG_DIR);
10740
10975
  if (outsideLinkAt(projectDir, INSTALL_LOG_DIR) !== null) io.rm(recordDir);
10741
10976
  else {
10742
- for (const rel of RECORD_FILES) io.rm(join33(projectDir, rel));
10977
+ for (const rel of RECORD_FILES) io.rm(join34(projectDir, rel));
10743
10978
  pruneEmptyDirsUpward(projectDir, recordDir);
10744
10979
  }
10745
10980
  if (outsideLinkAt(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
10746
10981
  io.rm(legacyInstallLogPath(projectDir));
10747
- pruneEmptyDirsUpward(projectDir, join33(projectDir, LEGACY_INSTALL_LOG_DIR));
10982
+ pruneEmptyDirsUpward(projectDir, join34(projectDir, LEGACY_INSTALL_LOG_DIR));
10748
10983
  }
10749
10984
  io.log(` ${status.success("install log removed")}`);
10750
10985
  }
@@ -10760,7 +10995,7 @@ function afterFullRemoval(next, done) {
10760
10995
  if (back.size === 0) return next;
10761
10996
  const out = { ...next, rootFiles: withoutRestored(next.rootFiles, done.displaced) };
10762
10997
  const stays = (prefix) => (f) => !back.has(`${prefix}${f.path}`);
10763
- 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));
10764
10999
  if (out.skillFiles !== void 0) out.skillFiles = out.skillFiles.filter(stays(SKILLS_DIR3));
10765
11000
  if (out.externalFiles !== void 0) out.externalFiles = out.externalFiles.filter(stays(""));
10766
11001
  const anchor = out.templates.rootClaudeMd;
@@ -10784,7 +11019,12 @@ function dryRunLines(plan, installLog, projectDir, assetsOnly, rootFiles, harnes
10784
11019
  if (plan.reverseSteps.length === 0 && copies.length === 0) {
10785
11020
  lines.push(c.dim(" (no project-scope assets to reverse)"));
10786
11021
  }
10787
- 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
+ );
10788
11028
  lines.push(...globalCopyLines(copies, true));
10789
11029
  lines.push(
10790
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)"
@@ -10808,7 +11048,10 @@ function dryRunLines(plan, installLog, projectDir, assetsOnly, rootFiles, harnes
10808
11048
  willBeGone: new Set(plannedAssetFiles(plan, installLog)),
10809
11049
  write: false
10810
11050
  });
10811
- lines.push(...displacedLines(preview, true));
11051
+ lines.push(
11052
+ ...displacedLines(preview, true),
11053
+ ...byHandLines([], preview, projectDir, installLog)
11054
+ );
10812
11055
  }
10813
11056
  lines.push(...advisoryLines(plan, projectDir, rootFiles, globalTrust), "");
10814
11057
  return lines;
@@ -10848,7 +11091,7 @@ function backupArtifactAdvisoryLines(projectDir) {
10848
11091
  }
10849
11092
  }
10850
11093
  for (const dir of BACKUP_SCAN_DIRS) {
10851
- const abs = join33(projectDir, dir);
11094
+ const abs = join34(projectDir, dir);
10852
11095
  if (!existsSync24(abs) || outsideLinkAt(projectDir, dir) !== null) continue;
10853
11096
  for (const rel of listFilesRecursive(abs)) {
10854
11097
  if (BACKUP_SUFFIX.test(rel)) found.push(`${dir}/${rel}`);
@@ -10859,16 +11102,16 @@ function backupArtifactAdvisoryLines(projectDir) {
10859
11102
  c.yellow(
10860
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):`
10861
11104
  ),
10862
- ...found.sort().map((f) => c.dim(` ${f}`))
11105
+ ...found.sort().map((f) => c.dim(` ${screenText(f)}`))
10863
11106
  ];
10864
11107
  }
10865
11108
  function rootFileAdvisoryLines(rootFiles, projectDir) {
10866
11109
  const header = "[ROOT] `.claude/` \uBC16\uC5D0 \uB0A8\uB294 \uAC83 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):";
10867
- const present = rootFiles.filter((f) => existsSync24(join33(projectDir, f.path)));
11110
+ const present = rootFiles.filter((f) => existsSync24(join34(projectDir, f.path)));
10868
11111
  if (present.length === 0) return [];
10869
11112
  const outside = /* @__PURE__ */ new Map();
10870
11113
  const inside = present.filter((f) => {
10871
- const o = outsideProjectTarget(projectDir, join33(projectDir, f.path));
11114
+ const o = outsideProjectTarget(projectDir, join34(projectDir, f.path));
10872
11115
  if (o === null) return true;
10873
11116
  const group = outside.get(o.link) ?? { link: o, files: [] };
10874
11117
  group.files.push(f.path);
@@ -10880,13 +11123,13 @@ function rootFileAdvisoryLines(rootFiles, projectDir) {
10880
11123
  c.yellow(header),
10881
11124
  ...[...outside.values()].map(
10882
11125
  ({ link: o, files }) => c.dim(
10883
- ` \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)`
10884
11127
  )
10885
11128
  ),
10886
11129
  ...inside.flatMap((f) => [
10887
- c.dim(` \xB7 ${f.path} \u2014 ${rootFileMeaning(f, projectDir)}`),
11130
+ c.dim(` \xB7 ${screenText(f.path)} \u2014 ${rootFileMeaning(f, projectDir)}`),
10888
11131
  // displaced 의 notes 는 백업 경로 하나 — 위 줄이 이미 댔다
10889
- ...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(" / ")}`)] : []
10890
11133
  ])
10891
11134
  ];
10892
11135
  }
@@ -10907,7 +11150,7 @@ function rootFileMeaning(f, projectDir) {
10907
11150
  case "displaced": {
10908
11151
  const backup = f.notes[0];
10909
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)";
10910
- return existsSync24(join33(projectDir, backup)) ? `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC740 ${backup} \uC5D0 \uC788\uB2E4` : `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC758 \uBC31\uC5C5(${backup})\uC774 \uB354\uB294 \uC5C6\uB2E4`;
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`;
10911
11154
  }
10912
11155
  }
10913
11156
  }
@@ -10989,15 +11232,15 @@ function agentsMdRerenderSet(target, remaining, log, projectDir) {
10989
11232
  const set = agentsMdClis(remaining);
10990
11233
  if (set.length === 0) return null;
10991
11234
  const recorded2 = (log.externalFiles ?? []).some((f) => f.path === AGENTS_MD2) || (log.portions ?? []).some((p2) => p2.path === AGENTS_MD2);
10992
- if (!existsSync24(join33(projectDir, AGENTS_MD2))) return null;
10993
- if (!recorded2 && !isLegacyHarnessAgentsMd(readTextOrEmpty(join33(projectDir, AGENTS_MD2)))) {
11235
+ if (!existsSync24(join34(projectDir, AGENTS_MD2))) return null;
11236
+ if (!recorded2 && !isLegacyHarnessAgentsMd(readTextOrEmpty(join34(projectDir, AGENTS_MD2)))) {
10994
11237
  return null;
10995
11238
  }
10996
11239
  return set;
10997
11240
  }
10998
11241
  function readTextOrEmpty(abs) {
10999
11242
  try {
11000
- return readFileSync25(abs, "utf8");
11243
+ return readFileSync26(abs, "utf8");
11001
11244
  } catch {
11002
11245
  return "";
11003
11246
  }
@@ -11067,7 +11310,7 @@ function settleCliLog(ctx, target, remaining, done, anchorRemoved) {
11067
11310
  function forgetRemoved(next, done) {
11068
11311
  const out = { ...next };
11069
11312
  const kept = (prefix) => (f) => !done.forgotten.has(`${prefix}${f.path}`);
11070
- 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));
11071
11314
  if (out.skillFiles !== void 0) out.skillFiles = out.skillFiles.filter(kept(SKILLS_DIR3));
11072
11315
  const survivors = (out.externalFiles ?? []).filter(kept(""));
11073
11316
  if (survivors.length > 0) out.externalFiles = survivors;
@@ -11121,9 +11364,9 @@ function runScopedRemoval(ctx, io, plan) {
11121
11364
  }
11122
11365
  for (const line of lines) io.log(line);
11123
11366
  const settled = plan.settle(done);
11124
- const agentsBefore = readTextOrEmpty(join33(projectDir, AGENTS_MD2));
11367
+ const agentsBefore = readTextOrEmpty(join34(projectDir, AGENTS_MD2));
11125
11368
  const rerender = plan.rerenderSet === null ? null : rerenderAgentsMd(ctx, plan.rerenderSet, settled);
11126
- const rewrote = rerender !== null && readTextOrEmpty(join33(projectDir, AGENTS_MD2)) !== agentsBefore;
11369
+ const rewrote = rerender !== null && readTextOrEmpty(join34(projectDir, AGENTS_MD2)) !== agentsBefore;
11127
11370
  if (rewrote) for (const line of rerenderLines(rerender)) io.log(line);
11128
11371
  const next = rerender === null ? settled : withRerender(settled, rerender);
11129
11372
  try {
@@ -11196,7 +11439,7 @@ function removeTrackAction(ctx, io) {
11196
11439
  }
11197
11440
  var TRACK_SCOPE = { shared: [], skeleton: [], folders: [], importBlock: false, anchor: false };
11198
11441
  function trackNotes(ctx, r, d2, write) {
11199
- const md = join33(ctx.projectDir, "CLAUDE.md");
11442
+ const md = join34(ctx.projectDir, "CLAUDE.md");
11200
11443
  const block = rootBlockNext(ctx, new Set(d2.removed));
11201
11444
  if (write && block !== null) {
11202
11445
  backupIfLossyUtf8(md);
@@ -11224,11 +11467,11 @@ function previewTrackRemoval(ctx, target) {
11224
11467
  }
11225
11468
  function rootBlockNext({ installLog, projectDir }, gone) {
11226
11469
  const anchor = installLog.templates?.rootClaudeMd?.path;
11227
- const md = join33(projectDir, "CLAUDE.md");
11470
+ const md = join34(projectDir, "CLAUDE.md");
11228
11471
  const text = safeExists(md) && outsideProjectTarget(projectDir, md) === null ? readTextOrEmpty(md) : "";
11229
- if (anchor === void 0 || !safeExists(join33(projectDir, anchor))) return null;
11472
+ if (anchor === void 0 || !safeExists(join34(projectDir, anchor))) return null;
11230
11473
  if (stripHarnessImport(text) === null) return null;
11231
- const stays = (id) => listFilesRecursive(join33(projectDir, SKILLS_DIR3, id)).some(
11474
+ const stays = (id) => listFilesRecursive(join34(projectDir, SKILLS_DIR3, id)).some(
11232
11475
  (f) => !gone.has(`${SKILLS_DIR3}${id}/${f}`)
11233
11476
  );
11234
11477
  const continuousSkills = CONTINUOUS_SKILLS.map((s) => s.id).filter(stays);
@@ -11239,8 +11482,8 @@ function settleTrackLog(ctx, target, remaining, done) {
11239
11482
  const { installLog, projectDir } = ctx;
11240
11483
  const tracks = installLog.spec.tracks.filter((t) => t !== target);
11241
11484
  const next = forgetRemoved({ ...installLog, spec: { ...installLog.spec, tracks } }, done);
11242
- const abs = join33(projectDir, INSTALLED_TRACKS);
11243
- const key = INSTALLED_TRACKS.slice(CLAUDE_DIR3.length);
11485
+ const abs = join34(projectDir, INSTALLED_TRACKS);
11486
+ const key = INSTALLED_TRACKS.slice(CLAUDE_DIR4.length);
11244
11487
  const prior = installLog.policyFiles?.find((f) => f.path === key)?.sha256 ?? hashContent(installedTracksText(installLog.spec.tracks.filter(isTrack)));
11245
11488
  const edited = safeExists(abs) && hashContent(readTextOrEmpty(abs)) !== prior;
11246
11489
  if (!installedClis(installLog).includes("claude")) return next;
@@ -11266,7 +11509,7 @@ var SKILLS_LOCK = "skills-lock.json";
11266
11509
  function withSkillsLock(rootFiles, assets, projectDir) {
11267
11510
  const out = [...rootFiles];
11268
11511
  const skills = assets.some((a) => a.method === "skill" && a.scope === "project");
11269
- if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync24(join33(projectDir, SKILLS_LOCK))) {
11512
+ if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync24(join34(projectDir, SKILLS_LOCK))) {
11270
11513
  out.push({ path: SKILLS_LOCK, change: "advisory", notes: [] });
11271
11514
  }
11272
11515
  return out;
@@ -11277,7 +11520,7 @@ function remnantFor(path, log, harnessRoot, projectDir, wiringGone = []) {
11277
11520
  const gone = (k2) => wiringGone.includes(`hooks/${k2.split("#")[1] ?? ""}`);
11278
11521
  return (disk) => {
11279
11522
  try {
11280
- const template = readFileSync25(join33(harnessRoot, "templates", "settings.json"), "utf8");
11523
+ const template = readFileSync26(join34(harnessRoot, "templates", "settings.json"), "utf8");
11281
11524
  const render = renderSettingsPortion(template, () => true);
11282
11525
  return [...legacySettingsSeed(disk, render, projectDir, { edited: true }).keys()].filter(
11283
11526
  (k2) => !k2.endsWith("{}") && !gone(k2)
@@ -11415,12 +11658,27 @@ function noReverseHints(asset, ctx) {
11415
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"];
11416
11659
  }
11417
11660
  if (asset.method !== "skill") return [];
11418
- const name = asset.detail.skill;
11419
- const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => existsSync24(join33(ctx.projectDir, p2)));
11661
+ const left = unlistedFoldersLeft(asset, ctx.projectDir);
11420
11662
  return [
11421
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"
11422
11664
  ];
11423
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
+ }
11424
11682
  function buildProjectReverseStep(asset, ctx) {
11425
11683
  const { spawn } = ctx;
11426
11684
  switch (asset.method) {
@@ -11444,10 +11702,12 @@ ${r.stdout || ""}`;
11444
11702
  case "skill": {
11445
11703
  const files = asset.files;
11446
11704
  if (files === void 0) return null;
11705
+ const hints = asset.filesPartial === true ? partialHints(asset, ctx.projectDir) : [];
11447
11706
  return {
11448
11707
  assetId: asset.id,
11449
11708
  label: `remove ${asset.id} \u2014 ${files.length} file(s) on record`,
11450
- execute: () => removeToolFiles(files, ctx.projectDir, ctx.rm)
11709
+ execute: () => removeToolFiles(files, ctx.projectDir, ctx.rm),
11710
+ ...hints.length > 0 ? { hints } : {}
11451
11711
  };
11452
11712
  }
11453
11713
  case "npm": {
@@ -11498,7 +11758,7 @@ function buildGlobalAdvisoryCmd(asset) {
11498
11758
  return "(no standard reverse \u2014 manual)";
11499
11759
  }
11500
11760
  }
11501
- var CLAUDE_DIR3 = ".claude/";
11761
+ var CLAUDE_DIR4 = ".claude/";
11502
11762
  var LEGACY_LOG = `${LEGACY_INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`;
11503
11763
  var SKILLS_DIR3 = ".claude/skills/";
11504
11764
  var CLI_FOLDERS2 = [".claude", ".codex", ".opencode"];
@@ -11543,7 +11803,7 @@ function removeHarnessFiles(log, projectDir, scope, io) {
11543
11803
  }
11544
11804
  const judged = new Set(candidates);
11545
11805
  for (const path of legacyFileCandidates()) {
11546
- if (judged.has(path) || !scope.has(path) || !safeExists(join33(projectDir, path))) continue;
11806
+ if (judged.has(path) || !scope.has(path) || !safeExists(join34(projectDir, path))) continue;
11547
11807
  if (recorded(log, path, { isTarget }).state !== "none") continue;
11548
11808
  removeRecordedFile(path, { log, projectDir, io, out, isTarget, pruneFrom });
11549
11809
  }
@@ -11569,9 +11829,9 @@ function removeHarnessFiles(log, projectDir, scope, io) {
11569
11829
  write: io.write
11570
11830
  });
11571
11831
  if (io.write) {
11572
- for (const p2 of pruneFrom) pruneEmptyDirsUpward(projectDir, dirname8(join33(projectDir, p2)));
11832
+ for (const p2 of pruneFrom) pruneEmptyDirsUpward(projectDir, dirname8(join34(projectDir, p2)));
11573
11833
  for (const d2 of scope.skeleton) {
11574
- const abs = join33(projectDir, d2);
11834
+ const abs = join34(projectDir, d2);
11575
11835
  if (outsideProjectTarget(projectDir, abs) !== null) continue;
11576
11836
  pruneEmptyDirsUpward(projectDir, abs);
11577
11837
  }
@@ -11580,7 +11840,7 @@ function removeHarnessFiles(log, projectDir, scope, io) {
11580
11840
  }
11581
11841
  function harnessCandidates(log) {
11582
11842
  const paths = [
11583
- ...(log.policyFiles ?? []).map((f) => `${CLAUDE_DIR3}${f.path}`),
11843
+ ...(log.policyFiles ?? []).map((f) => `${CLAUDE_DIR4}${f.path}`),
11584
11844
  ...(log.skillFiles ?? []).map((f) => `${SKILLS_DIR3}${f.path}`),
11585
11845
  ...(log.externalFiles ?? []).map((f) => f.path),
11586
11846
  // 헬퍼 — writer 기록은 `externalFiles` 에 sha 로 적지만 옛 기록(≤v26.161.0)엔 항목이 없다. `recorded()` 가 가른다
@@ -11607,17 +11867,17 @@ function helperSources() {
11607
11867
  function templateNext(harnessRoot, log, path) {
11608
11868
  if (path === INSTALLED_TRACKS) return installedTracksText(log.spec.tracks.filter(isTrack));
11609
11869
  const helper = helperSources().get(path);
11610
- if (helper === void 0 && !path.startsWith(CLAUDE_DIR3)) return null;
11870
+ if (helper === void 0 && !path.startsWith(CLAUDE_DIR4)) return null;
11611
11871
  try {
11612
- const source = helper ?? path.slice(CLAUDE_DIR3.length);
11613
- return readFileSync25(join33(harnessRoot, "templates", source), "utf8");
11872
+ const source = helper ?? path.slice(CLAUDE_DIR4.length);
11873
+ return readFileSync26(join34(harnessRoot, "templates", source), "utf8");
11614
11874
  } catch {
11615
11875
  return null;
11616
11876
  }
11617
11877
  }
11618
11878
  function removeRecordedFile(path, ctx) {
11619
11879
  const { log, projectDir, io, out } = ctx;
11620
- const abs = join33(projectDir, path);
11880
+ const abs = join34(projectDir, path);
11621
11881
  let foreign;
11622
11882
  try {
11623
11883
  foreign = foreignOwnedTarget(projectDir, path);
@@ -11641,7 +11901,7 @@ function removeRecordedFile(path, ctx) {
11641
11901
  }
11642
11902
  let st2;
11643
11903
  try {
11644
- st2 = lstatSync7(abs, { throwIfNoEntry: false });
11904
+ st2 = lstatSync8(abs, { throwIfNoEntry: false });
11645
11905
  } catch {
11646
11906
  out.unjudged.push(path);
11647
11907
  return;
@@ -11659,7 +11919,7 @@ function removeRecordedFile(path, ctx) {
11659
11919
  return;
11660
11920
  }
11661
11921
  if (st2.isSymbolicLink()) effective = real;
11662
- disk = readFileSync25(effective, "utf8");
11922
+ disk = readFileSync26(effective, "utf8");
11663
11923
  } catch {
11664
11924
  out.unjudged.push(path);
11665
11925
  return;
@@ -11706,11 +11966,11 @@ function mayReclaimLegacy(log, projectDir, path) {
11706
11966
  function removeLegacyHookWiring(log, projectDir, scope, io, removed) {
11707
11967
  if (!scope.has(SETTINGS_TARGET) || !installedClis(log).includes("claude")) return [];
11708
11968
  if ((log.portions ?? []).some((p2) => p2.path === SETTINGS_TARGET)) return [];
11709
- const abs = join33(projectDir, SETTINGS_TARGET);
11969
+ const abs = join34(projectDir, SETTINGS_TARGET);
11710
11970
  if (!safeExists(abs) || outsideProjectTarget(projectDir, abs) !== null) return [];
11711
- const claudeDir = join33(projectDir, ".claude");
11971
+ const claudeDir = join34(projectDir, ".claude");
11712
11972
  const gone = LEGACY_HOOK_REFS.filter(
11713
- (r) => !RETIRED_HOOK_WIRING_REFS.includes(r) && (removed.includes(`${CLAUDE_DIR3}${r}`) || !safeExists(join33(claudeDir, r)))
11973
+ (r) => !RETIRED_HOOK_WIRING_REFS.includes(r) && (removed.includes(`${CLAUDE_DIR4}${r}`) || !safeExists(join34(claudeDir, r)))
11714
11974
  );
11715
11975
  return removeRetiredHookRefs(abs, claudeDir, gone, { evenIfScriptExists: true, write: io.write });
11716
11976
  }
@@ -11731,7 +11991,7 @@ function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
11731
11991
  };
11732
11992
  for (const path of paths) {
11733
11993
  if (!relevant(path)) continue;
11734
- const abs = join33(projectDir, path);
11994
+ const abs = join34(projectDir, path);
11735
11995
  if (safeExists(abs)) {
11736
11996
  const o = outsideProjectTarget(projectDir, abs);
11737
11997
  if (o !== null) {
@@ -11742,7 +12002,7 @@ function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
11742
12002
  if (path === SETTINGS_TARGET && safeExists(abs)) {
11743
12003
  out.retiredWiring = removeRetiredHookRefs(
11744
12004
  abs,
11745
- join33(projectDir, ".claude"),
12005
+ join34(projectDir, ".claude"),
11746
12006
  RETIRED_HOOK_WIRING_REFS,
11747
12007
  { evenIfScriptExists: true, write: io.write }
11748
12008
  );
@@ -11770,7 +12030,7 @@ function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
11770
12030
  }
11771
12031
  }
11772
12032
  function removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom) {
11773
- const abs = join33(projectDir, path);
12033
+ const abs = join34(projectDir, path);
11774
12034
  if (!existsSync24(abs)) {
11775
12035
  out.forgotten.add(path);
11776
12036
  return true;
@@ -11783,8 +12043,8 @@ function removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom) {
11783
12043
  out.unjudged.push(path);
11784
12044
  return true;
11785
12045
  }
11786
- if (lstatSync7(abs).isSymbolicLink()) effective = real;
11787
- current = readFileSync25(effective, "utf8");
12046
+ if (lstatSync8(abs).isSymbolicLink()) effective = real;
12047
+ current = readFileSync26(effective, "utf8");
11788
12048
  } catch {
11789
12049
  out.unjudged.push(path);
11790
12050
  return true;
@@ -11852,14 +12112,16 @@ function displacedLines(displaced, preview) {
11852
12112
  const restore = displaced.filter((d2) => d2.verdict === "restore");
11853
12113
  if (preview && restore.length > 0) {
11854
12114
  lines.push(
11855
- ` \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 ")}`
11856
12116
  );
11857
12117
  }
11858
12118
  for (const d2 of displaced) {
11859
12119
  if (d2.verdict === "restore" && !preview) {
11860
- 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
+ );
11861
12123
  } else if (d2.verdict === "advise") {
11862
- lines.push(` ${c.yellow("\u2298")} ${d2.path} \u2014 ${d2.line}`);
12124
+ lines.push(` ${c.yellow("\u2298")} ${screenText(d2.path)} \u2014 ${d2.line}`);
11863
12125
  }
11864
12126
  }
11865
12127
  return lines;
@@ -11961,13 +12223,14 @@ function harnessRemovalLines(r, log, projectDir, scope, preview, alsoGone = [])
11961
12223
  }
11962
12224
  lines.push(...sharedStripLines(r.shared, preview));
11963
12225
  lines.push(...leftFolderLines(r, log, projectDir, scope, preview, alsoGone));
12226
+ lines.push(...byHandLines(r.offSlot, r.displaced, projectDir, log));
11964
12227
  return lines;
11965
12228
  }
11966
12229
  function outsideLines(outside, log) {
11967
12230
  const byLink = /* @__PURE__ */ new Map();
11968
12231
  for (const o of outside) if (!byLink.has(o.link)) byLink.set(o.link, o);
11969
12232
  return [...byLink.values()].map(
11970
- (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)
11971
12234
  );
11972
12235
  }
11973
12236
  function leftFolderLines(r, log, projectDir, scope, preview, alsoGone) {
@@ -12009,19 +12272,19 @@ function yoursUnder(projectDir, dir) {
12009
12272
  }
12010
12273
  for (const e2 of entries) {
12011
12274
  const name = `${prefix}${e2.name}`;
12012
- if (e2.isDirectory()) walk(join33(abs2, e2.name), `${name}/`);
12275
+ if (e2.isDirectory()) walk(join34(abs2, e2.name), `${name}/`);
12013
12276
  else if (BACKUP_SUFFIX.test(e2.name)) continue;
12014
12277
  else if (e2.isSymbolicLink()) {
12015
12278
  let target = "?";
12016
12279
  try {
12017
- target = readlinkSync2(join33(abs2, e2.name));
12280
+ target = readlinkSync2(join34(abs2, e2.name));
12018
12281
  } catch {
12019
12282
  }
12020
12283
  out.push({ name, shown: `${name} (your link \u2192 ${target})` });
12021
12284
  } else out.push({ name, shown: name });
12022
12285
  }
12023
12286
  };
12024
- const abs = join33(projectDir, dir);
12287
+ const abs = join34(projectDir, dir);
12025
12288
  try {
12026
12289
  if (!statSync4(abs).isDirectory()) return [];
12027
12290
  } catch {
@@ -12033,7 +12296,7 @@ function yoursUnder(projectDir, dir) {
12033
12296
  function assetsLeftLines(log, projectDir, scope) {
12034
12297
  const lines = [];
12035
12298
  for (const a of log.assets) {
12036
- const here = (a.files ?? []).map((f) => f.path).filter((p2) => scope.has(p2) && safeExists(join33(projectDir, p2)));
12299
+ const here = (a.files ?? []).map((f) => f.path).filter((p2) => scope.has(p2) && safeExists(join34(projectDir, p2)));
12037
12300
  if (here.length === 0) continue;
12038
12301
  const slots = [...new Set(here.map((p2) => p2.split("/").slice(0, 3).join("/")))].sort();
12039
12302
  lines.push(
@@ -12045,7 +12308,7 @@ function assetsLeftLines(log, projectDir, scope) {
12045
12308
  function removeAnchor(log, projectDir, io) {
12046
12309
  const rootMd = log.templates?.rootClaudeMd;
12047
12310
  if (!rootMd) return { removed: false, lines: [] };
12048
- const abs = join33(projectDir, rootMd.path);
12311
+ const abs = join34(projectDir, rootMd.path);
12049
12312
  if (!existsSync24(abs)) return { removed: false, lines: [] };
12050
12313
  const outside = outsideProjectTarget(projectDir, abs);
12051
12314
  if (outside !== null) return { removed: false, lines: outsideLines([outside], log) };
@@ -12054,7 +12317,7 @@ function removeAnchor(log, projectDir, io) {
12054
12317
  }
12055
12318
  let disk;
12056
12319
  try {
12057
- disk = readFileSync25(abs, "utf8");
12320
+ disk = readFileSync26(abs, "utf8");
12058
12321
  } catch {
12059
12322
  return {
12060
12323
  removed: false,
@@ -12099,7 +12362,7 @@ function removeAnchor(log, projectDir, io) {
12099
12362
  var AGENTS_MD2 = "AGENTS.md";
12100
12363
  function readAgentsMdTemplate(harnessRoot) {
12101
12364
  try {
12102
- return readFileSync25(join33(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
12365
+ return readFileSync26(join34(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
12103
12366
  } catch {
12104
12367
  return null;
12105
12368
  }
@@ -12125,14 +12388,14 @@ function agentsMdRemainder(current, harnessRoot) {
12125
12388
  return { ok: true, remainder: strip.remainder, strip };
12126
12389
  }
12127
12390
  function removeUnrecordedLegacyAgentsMd(path, projectDir, io, out, pruneFrom) {
12128
- const abs = join33(projectDir, path);
12391
+ const abs = join34(projectDir, path);
12129
12392
  let effective = abs;
12130
12393
  let current;
12131
12394
  try {
12132
12395
  const real = realpathSync4(abs);
12133
12396
  if (!statSync4(real).isFile()) return false;
12134
- if (lstatSync7(abs).isSymbolicLink()) effective = real;
12135
- current = readFileSync25(effective, "utf8");
12397
+ if (lstatSync8(abs).isSymbolicLink()) effective = real;
12398
+ current = readFileSync26(effective, "utf8");
12136
12399
  } catch {
12137
12400
  return false;
12138
12401
  }
@@ -12190,33 +12453,95 @@ function globalCopyLines(copies, preview) {
12190
12453
  });
12191
12454
  }
12192
12455
  var ASSET_SLOTS = skillFolders(SKILL_ROOTS);
12193
- function offSlotLines(paths, projectDir) {
12194
- const lines = [];
12456
+ function offSlotGroups(paths, projectDir) {
12457
+ const literal = [];
12195
12458
  const byLink = /* @__PURE__ */ new Map();
12196
12459
  for (const path of paths) {
12197
12460
  const link = linkOnPath(projectDir, path);
12198
12461
  if (link === null) {
12199
- lines.push(offSlotLine(path, "lists it"));
12462
+ literal.push(path);
12200
12463
  continue;
12201
12464
  }
12202
- const g = byLink.get(link.link) ?? { target: link.target, n: 0 };
12203
- g.n += 1;
12465
+ const g = byLink.get(link.link) ?? { target: link.target, files: [] };
12466
+ g.files.push(path);
12204
12467
  byLink.set(link.link, g);
12205
12468
  }
12206
- for (const [link, g] of byLink) {
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) {
12207
12505
  lines.push(
12208
- ` ${c.yellow("\u2298")} ${link} \u2014 your link (\u2192 ${g.target}) leads outside the places this tool installs to; left ${g.n} file(s) on the install record under it. Not touched \u2014 remove by hand if intended.`
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
+ )
12209
12531
  );
12210
12532
  }
12211
12533
  return lines;
12212
12534
  }
12535
+ function isPlainPath(path) {
12536
+ return path.split("/").every((seg) => /^[A-Za-z0-9._][A-Za-z0-9._-]*$/.test(seg) && seg !== "." && seg !== "..");
12537
+ }
12213
12538
  function linkOnPath(projectDir, path) {
12214
12539
  const parts = path.split("/");
12215
12540
  for (let i = 1; i <= parts.length; i++) {
12216
12541
  const rel = parts.slice(0, i).join("/");
12217
12542
  try {
12218
- if (lstatSync7(join33(projectDir, rel), { throwIfNoEntry: false })?.isSymbolicLink() === true) {
12219
- return { link: rel, target: readlinkSync2(join33(projectDir, rel)) };
12543
+ if (lstatSync8(join34(projectDir, rel), { throwIfNoEntry: false })?.isSymbolicLink() === true) {
12544
+ return { link: rel, target: readlinkSync2(join34(projectDir, rel)) };
12220
12545
  }
12221
12546
  } catch {
12222
12547
  return null;
@@ -12225,16 +12550,16 @@ function linkOnPath(projectDir, path) {
12225
12550
  return null;
12226
12551
  }
12227
12552
  function offSlotLine(path, what) {
12228
- return ` ${c.yellow("\u2298")} ${path} left \u2014 the install record ${what}, but it is (or leads) outside the places this tool installs to. Not touched.`;
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.`;
12229
12554
  }
12230
12555
  function removeToolFiles(files, projectDir, rm) {
12231
12556
  const notes = [];
12232
12557
  const removed = [];
12233
12558
  let failed = 0;
12234
- const pairs = new Set(files.filter((f) => isAppleDoublePath(join33(projectDir, f.path))));
12559
+ const pairs = new Set(files.filter((f) => isAppleDoublePath(join34(projectDir, f.path))));
12235
12560
  for (const file of files) {
12236
12561
  const { path, sha256 } = file;
12237
- const abs = join33(projectDir, path);
12562
+ const abs = join34(projectDir, path);
12238
12563
  if (pairs.has(file)) {
12239
12564
  try {
12240
12565
  rm(abs);
@@ -12244,7 +12569,7 @@ function removeToolFiles(files, projectDir, rm) {
12244
12569
  }
12245
12570
  let isLink;
12246
12571
  try {
12247
- isLink = lstatSync7(abs).isSymbolicLink();
12572
+ isLink = lstatSync8(abs).isSymbolicLink();
12248
12573
  } catch {
12249
12574
  continue;
12250
12575
  }
@@ -12264,7 +12589,7 @@ function removeToolFiles(files, projectDir, rm) {
12264
12589
  continue;
12265
12590
  }
12266
12591
  try {
12267
- if (hashContent(readFileSync25(abs, "utf8")) !== sha256) {
12592
+ if (hashContent(readFileSync26(abs, "utf8")) !== sha256) {
12268
12593
  const backup = backupFile(abs);
12269
12594
  notes.push(`backed up ${path} \u2192 ${relative9(projectDir, backup)} (changed since install)`);
12270
12595
  }
@@ -12275,7 +12600,7 @@ function removeToolFiles(files, projectDir, rm) {
12275
12600
  notes.push(`left ${path} \u2014 ${e2 instanceof Error ? e2.message : String(e2)}`);
12276
12601
  }
12277
12602
  }
12278
- for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname8(join33(projectDir, path)));
12603
+ for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname8(join34(projectDir, path)));
12279
12604
  return failed > 0 ? { ok: false, message: `${failed} file(s) could not be removed`, notes } : { ok: true, notes };
12280
12605
  }
12281
12606
  function pruneEmptyDirsUpward(projectDir, startDir) {
@@ -12299,8 +12624,8 @@ function stripRootImport(projectDir) {
12299
12624
  if (withoutImport === null) return false;
12300
12625
  const stripped = neutralizeScaffoldBanner(withoutImport);
12301
12626
  try {
12302
- backupIfLossyUtf8(join33(projectDir, "CLAUDE.md"));
12303
- writeFileSync8(join33(projectDir, "CLAUDE.md"), stripped, "utf8");
12627
+ backupIfLossyUtf8(join34(projectDir, "CLAUDE.md"));
12628
+ writeFileSync8(join34(projectDir, "CLAUDE.md"), stripped, "utf8");
12304
12629
  return true;
12305
12630
  } catch {
12306
12631
  return false;
@@ -12322,13 +12647,13 @@ function importOutsideLine(o, anchorGone) {
12322
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.`;
12323
12648
  }
12324
12649
  function rootClaudeMdOutside(projectDir) {
12325
- return outsideProjectTarget(projectDir, join33(projectDir, "CLAUDE.md"));
12650
+ return outsideProjectTarget(projectDir, join34(projectDir, "CLAUDE.md"));
12326
12651
  }
12327
12652
  function readRootClaudeMd(projectDir) {
12328
- const path = join33(projectDir, "CLAUDE.md");
12653
+ const path = join34(projectDir, "CLAUDE.md");
12329
12654
  if (!existsSync24(path)) return null;
12330
12655
  try {
12331
- return readFileSync25(path, "utf8");
12656
+ return readFileSync26(path, "utf8");
12332
12657
  } catch {
12333
12658
  return null;
12334
12659
  }