@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-
|
|
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
|
|
1952
|
-
import { basename as basename6, join as
|
|
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,
|
|
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 =
|
|
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 =
|
|
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")
|
|
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
|
|
3554
|
-
import { basename as basename3, dirname as dirname5, join as
|
|
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
|
|
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(
|
|
3581
|
-
const dir = resolveWriteTarget(
|
|
3582
|
-
return dir === null ? null :
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
3685
|
+
const abs = join22(projectDir, backup);
|
|
3608
3686
|
const hit = outsideProjectTarget(projectDir, dirname5(abs));
|
|
3609
|
-
return hit === null ? null :
|
|
3687
|
+
return hit === null ? null : join22(hit.target, basename3(abs));
|
|
3610
3688
|
}
|
|
3611
3689
|
function isBackupOf(path, backup) {
|
|
3612
|
-
return backupOriginal(
|
|
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 =
|
|
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 =
|
|
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 ${
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
3718
|
-
|
|
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
|
-
|
|
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
|
|
3830
|
+
lstatSync as lstatSync5,
|
|
3731
3831
|
mkdirSync as mkdirSync6,
|
|
3732
3832
|
readdirSync as readdirSync3,
|
|
3733
|
-
readFileSync as
|
|
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
|
|
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,
|
|
3750
|
-
for (const rel of listFilesRecursive(
|
|
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(
|
|
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 =
|
|
3772
|
-
if (st2.isDirectory()) out.set(
|
|
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 ?
|
|
3888
|
+
const path = xdg ? join24(xdg, "skills", ".skill-lock.json") : join24(homedir2(), ".agents", ".skill-lock.json");
|
|
3786
3889
|
try {
|
|
3787
|
-
const lock = JSON.parse(
|
|
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(
|
|
3888
|
-
|
|
3889
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
4171
|
+
dir = claimBackupDir(join24(projectDir, root), now);
|
|
4051
4172
|
asides.set(root, dir);
|
|
4052
4173
|
}
|
|
4053
|
-
const aside =
|
|
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
|
-
|
|
4078
|
-
(r) =>
|
|
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
|
|
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
|
-
|
|
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
|
|
4284
|
+
const out = assets.flatMap((a) => {
|
|
4130
4285
|
const prior = ctx.log?.assets.find((r) => r.id === a.id);
|
|
4131
|
-
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
4327
|
+
return join24(process.env.CLAUDE_CONFIG_DIR?.trim() || join24(homedir2(), ".claude"), "skills");
|
|
4169
4328
|
}
|
|
4170
|
-
return
|
|
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 =
|
|
4335
|
+
const abs = join24(projectDir, slot);
|
|
4177
4336
|
try {
|
|
4178
|
-
if (
|
|
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 =
|
|
4368
|
+
const file = join24(abs, rel);
|
|
4210
4369
|
try {
|
|
4211
|
-
const mine =
|
|
4212
|
-
const same = homeExists && readOrNull(
|
|
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 (${
|
|
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 :
|
|
4412
|
+
return dir === null ? null : join24(dir, basename4(abs));
|
|
4246
4413
|
}
|
|
4247
4414
|
function readOrNull(abs) {
|
|
4248
4415
|
try {
|
|
4249
|
-
return
|
|
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(
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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,
|
|
4284
|
-
|
|
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)))
|
|
4287
|
-
|
|
4288
|
-
|
|
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 !==
|
|
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(
|
|
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)
|
|
4321
|
-
else now =
|
|
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 ?
|
|
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
|
|
4343
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
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 {
|
|
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) :
|
|
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(
|
|
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 =
|
|
4732
|
+
const path = join24(homedir2(), ".claude/plugins/installed_plugins.json");
|
|
4508
4733
|
if (!existsSync17(path)) return void 0;
|
|
4509
|
-
const data = JSON.parse(
|
|
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 ?
|
|
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
|
|
4542
|
-
import { join as
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
4632
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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 :
|
|
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
|
|
4695
|
-
import { join as
|
|
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(
|
|
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) =>
|
|
4947
|
+
const at2 = (rel) => join28(projectDir, rel);
|
|
4723
4948
|
const text = (rel) => {
|
|
4724
4949
|
try {
|
|
4725
|
-
return
|
|
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
|
|
5014
|
+
lstatSync as lstatSync7,
|
|
4790
5015
|
readdirSync as readdirSync4,
|
|
4791
|
-
readFileSync as
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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 = [
|
|
5236
|
-
if (clis.some((cli) => cli !== "claude")) dirs.push(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
5343
|
-
|
|
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 =
|
|
5428
|
-
const bundled = new Set(listFilesRecursive(
|
|
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 =
|
|
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" ?
|
|
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 =
|
|
5508
|
-
if (
|
|
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(
|
|
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 =
|
|
5520
|
-
const file =
|
|
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(
|
|
5778
|
+
if (existsSync19(join29(projectDir, LEGACY_ANCHOR_FILE2))) {
|
|
5553
5779
|
report.legacyAnchor = LEGACY_ANCHOR_FILE2;
|
|
5554
5780
|
}
|
|
5555
|
-
const templateMd =
|
|
5781
|
+
const templateMd = join29(templatesDir, "CLAUDE.md");
|
|
5556
5782
|
if (!existsSync19(templateMd)) return;
|
|
5557
|
-
const anchor =
|
|
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 =
|
|
5562
|
-
if (current !==
|
|
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 =
|
|
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 =
|
|
5821
|
+
const target = join29(projectDir, "CLAUDE.md");
|
|
5596
5822
|
if (outside.skip(target)) return false;
|
|
5597
|
-
const existing = existsSync19(target) ?
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
5931
|
-
const targetFile =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
6234
|
+
const settingsPath = join30(projectDir, ".claude", "settings.json");
|
|
6009
6235
|
if (existsSync20(settingsPath)) {
|
|
6010
6236
|
try {
|
|
6011
|
-
JSON.parse(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
6468
|
-
|
|
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(
|
|
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
|
-
|
|
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) =>
|
|
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 =
|
|
7007
|
+
const target = join30(projectDir, "CLAUDE.md");
|
|
6781
7008
|
if (writer.skipOutside(target)) return null;
|
|
6782
|
-
const existing = existsSync20(target) ?
|
|
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
|
|
6815
|
-
import { join as
|
|
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
|
|
6871
|
-
import { basename as basename7, join as
|
|
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(
|
|
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(
|
|
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 =
|
|
6911
|
-
const existing = existsSync21(rootClaude) ?
|
|
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 [
|
|
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(
|
|
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(
|
|
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 =
|
|
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:
|
|
7348
|
+
file: join32(abs, e2.name)
|
|
7122
7349
|
})) : [];
|
|
7123
7350
|
};
|
|
7124
|
-
const rules = surface === "claude" ? mdFiles(
|
|
7125
|
-
const slot = hasClaude ?
|
|
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:
|
|
7355
|
+
file: join32(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
|
|
7129
7356
|
})).filter((e2) => existsSync22(e2.file));
|
|
7130
|
-
const agents = hasClaude ? mdFiles(
|
|
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",
|
|
7143
|
-
return files.map((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(
|
|
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(
|
|
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
|
|
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
|
|
8525
|
-
import { join as
|
|
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 =
|
|
8630
|
-
const modified = existsSync23(path) && hashContent(
|
|
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(
|
|
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
|
|
8898
|
+
lstatSync as lstatSync8,
|
|
8666
8899
|
readdirSync as readdirSync7,
|
|
8667
|
-
readFileSync as
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 ?? []
|
|
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 =
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
10993
|
-
if (!recorded2 && !isLegacyHarnessAgentsMd(readTextOrEmpty(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
11472
|
+
if (anchor === void 0 || !safeExists(join34(projectDir, anchor))) return null;
|
|
11230
11473
|
if (stripHarnessImport(text) === null) return null;
|
|
11231
|
-
const stays = (id) => listFilesRecursive(
|
|
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 =
|
|
11243
|
-
const key = INSTALLED_TRACKS.slice(
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
11832
|
+
for (const p2 of pruneFrom) pruneEmptyDirsUpward(projectDir, dirname8(join34(projectDir, p2)));
|
|
11573
11833
|
for (const d2 of scope.skeleton) {
|
|
11574
|
-
const abs =
|
|
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) => `${
|
|
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(
|
|
11870
|
+
if (helper === void 0 && !path.startsWith(CLAUDE_DIR4)) return null;
|
|
11611
11871
|
try {
|
|
11612
|
-
const source = helper ?? path.slice(
|
|
11613
|
-
return
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
11969
|
+
const abs = join34(projectDir, SETTINGS_TARGET);
|
|
11710
11970
|
if (!safeExists(abs) || outsideProjectTarget(projectDir, abs) !== null) return [];
|
|
11711
|
-
const claudeDir =
|
|
11971
|
+
const claudeDir = join34(projectDir, ".claude");
|
|
11712
11972
|
const gone = LEGACY_HOOK_REFS.filter(
|
|
11713
|
-
(r) => !RETIRED_HOOK_WIRING_REFS.includes(r) && (removed.includes(`${
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
11787
|
-
current =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 (
|
|
12135
|
-
current =
|
|
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
|
|
12194
|
-
const
|
|
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
|
-
|
|
12462
|
+
literal.push(path);
|
|
12200
12463
|
continue;
|
|
12201
12464
|
}
|
|
12202
|
-
const g = byLink.get(link.link) ?? { target: link.target,
|
|
12203
|
-
g.
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
12219
|
-
return { link: rel, target: readlinkSync2(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
12303
|
-
writeFileSync8(
|
|
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,
|
|
12650
|
+
return outsideProjectTarget(projectDir, join34(projectDir, "CLAUDE.md"));
|
|
12326
12651
|
}
|
|
12327
12652
|
function readRootClaudeMd(projectDir) {
|
|
12328
|
-
const path =
|
|
12653
|
+
const path = join34(projectDir, "CLAUDE.md");
|
|
12329
12654
|
if (!existsSync24(path)) return null;
|
|
12330
12655
|
try {
|
|
12331
|
-
return
|
|
12656
|
+
return readFileSync26(path, "utf8");
|
|
12332
12657
|
} catch {
|
|
12333
12658
|
return null;
|
|
12334
12659
|
}
|