@uzysjung/agent-harness 26.175.0 → 26.176.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -126,6 +126,7 @@ import {
|
|
|
126
126
|
resolveBundleRoot,
|
|
127
127
|
resolveRules,
|
|
128
128
|
resolveScope,
|
|
129
|
+
resolveWriteTarget,
|
|
129
130
|
retiredMcpNames,
|
|
130
131
|
scaffoldBanner,
|
|
131
132
|
selectableMcpServers,
|
|
@@ -144,7 +145,7 @@ import {
|
|
|
144
145
|
withoutAppleDouble,
|
|
145
146
|
wrapHarnessBlock,
|
|
146
147
|
writeInstallLog
|
|
147
|
-
} from "./chunk-
|
|
148
|
+
} from "./chunk-HB6UJWC6.js";
|
|
148
149
|
|
|
149
150
|
// node_modules/sisteransi/src/index.js
|
|
150
151
|
var require_src = __commonJS({
|
|
@@ -1097,6 +1098,9 @@ var LINES = {
|
|
|
1097
1098
|
removed: "removed",
|
|
1098
1099
|
noChecksumRemoved: "no checksum on record \u2014 saved a copy, removed from live",
|
|
1099
1100
|
editedRemoved: "you edited it \u2014 saved as <file>.backup-<time>, removed from live",
|
|
1101
|
+
// #902 — 옛 판이 파일 목록 없이 깐 외부 스킬 폴더의 파일: 소유를 몰라 되살리지 않고 백업만(update 마다 같은 판정이라 폴더 밖을 권한다)
|
|
1102
|
+
noFileList: "no file list on record (an older version installed this skill) \u2014 saved as <file>.backup-<time>",
|
|
1103
|
+
noFileListRemoved: "no file list on record (an older version installed this skill) \u2014 saved as <file>.backup-<time>, removed from live (if it was yours, keep it outside this skill folder)",
|
|
1100
1104
|
sharedCreated: "wrote (harness part only)",
|
|
1101
1105
|
upsert: "wrote the harness part \u2014 yours stays",
|
|
1102
1106
|
strip: "removed the harness part \u2014 yours stays",
|
|
@@ -1685,7 +1689,14 @@ function withRecordedExclusions(spec, previous) {
|
|
|
1685
1689
|
],
|
|
1686
1690
|
[]
|
|
1687
1691
|
);
|
|
1688
|
-
|
|
1692
|
+
const keyExclude = [...excluded].filter(isKeyId);
|
|
1693
|
+
return {
|
|
1694
|
+
spec: {
|
|
1695
|
+
...specWithExclusions(spec, excluded),
|
|
1696
|
+
...keyExclude.length > 0 ? { keyExclude } : {}
|
|
1697
|
+
},
|
|
1698
|
+
excluded
|
|
1699
|
+
};
|
|
1689
1700
|
}
|
|
1690
1701
|
function thisRunExclusions(spec, previous, accepts) {
|
|
1691
1702
|
const excluded = /* @__PURE__ */ new Set([
|
|
@@ -1938,7 +1949,7 @@ function legacyGitignoreSeed(text, render, previous) {
|
|
|
1938
1949
|
// src/installer.ts
|
|
1939
1950
|
init_esm_shims();
|
|
1940
1951
|
import { chmodSync as chmodSync3, existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync21, writeFileSync as writeFileSync7 } from "fs";
|
|
1941
|
-
import { basename as basename6, join as
|
|
1952
|
+
import { basename as basename6, join as join29, relative as relative7, resolve, sep as sep8 } from "path";
|
|
1942
1953
|
|
|
1943
1954
|
// src/anchor-seed.ts
|
|
1944
1955
|
init_esm_shims();
|
|
@@ -3326,6 +3337,11 @@ function trackDefaultMcpAssetIds(harnessRoot, tracks) {
|
|
|
3326
3337
|
return id !== void 0 && anyTrack(tracks, r.pattern) ? [id] : [];
|
|
3327
3338
|
});
|
|
3328
3339
|
}
|
|
3340
|
+
function droppedMcpAssetIds(harnessRoot, excluded) {
|
|
3341
|
+
const rows = trackRows(harnessRoot);
|
|
3342
|
+
const assetOf = selectableMcpServers(rows);
|
|
3343
|
+
return droppedMcpServers(rows, excluded).flatMap((name) => assetOf.get(name) ?? []);
|
|
3344
|
+
}
|
|
3329
3345
|
function trackDefaultMcpNames(harnessRoot, tracks) {
|
|
3330
3346
|
const { templateMcpPath } = mcpSources(harnessRoot);
|
|
3331
3347
|
const base = existsSync15(templateMcpPath) ? Object.keys(JSON.parse(readFileSync14(templateMcpPath, "utf8")).mcpServers) : [];
|
|
@@ -3535,7 +3551,50 @@ function runCliTransforms(params) {
|
|
|
3535
3551
|
// src/displaced.ts
|
|
3536
3552
|
init_esm_shims();
|
|
3537
3553
|
import { lstatSync as lstatSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync } from "fs";
|
|
3538
|
-
import { basename as basename3, dirname as dirname5, join as
|
|
3554
|
+
import { basename as basename3, dirname as dirname5, join as join21, posix as posix2 } from "path";
|
|
3555
|
+
|
|
3556
|
+
// src/harness-slot.ts
|
|
3557
|
+
init_esm_shims();
|
|
3558
|
+
import { isAbsolute, join as join20, posix, relative as relative4, sep as sep5 } from "path";
|
|
3559
|
+
var ownedOf = (...kinds) => [
|
|
3560
|
+
...new Set(
|
|
3561
|
+
Object.values(CLI_OWNERSHIP).flat().filter((o) => kinds.includes(o.kind)).map((o) => o.path)
|
|
3562
|
+
)
|
|
3563
|
+
];
|
|
3564
|
+
var HARNESS_FILES = {
|
|
3565
|
+
places: [
|
|
3566
|
+
...ownedOf("recorded", "anchor"),
|
|
3567
|
+
...buildManifest({ tracks: [] }).map((e2) => e2.target).filter(isCliNeutralTarget)
|
|
3568
|
+
],
|
|
3569
|
+
except: new Set(Object.keys(SHARED_FILES))
|
|
3570
|
+
};
|
|
3571
|
+
var ANCHOR_FILES = { places: ownedOf("anchor", "import-block") };
|
|
3572
|
+
function skillFolders(roots) {
|
|
3573
|
+
return { places: roots.map((r) => `${r}/`) };
|
|
3574
|
+
}
|
|
3575
|
+
function within(rel, slots) {
|
|
3576
|
+
if (slots.except?.has(rel) === true) return false;
|
|
3577
|
+
return slots.places.some((p2) => p2.endsWith("/") ? rel.startsWith(p2) : rel === p2);
|
|
3578
|
+
}
|
|
3579
|
+
function realRel(projectDir, norm, follow) {
|
|
3580
|
+
const target = follow ? resolveWriteTarget(join20(projectDir, norm)) : (() => {
|
|
3581
|
+
const dir = resolveWriteTarget(join20(projectDir, posix.dirname(norm)));
|
|
3582
|
+
return dir === null ? null : join20(dir, posix.basename(norm));
|
|
3583
|
+
})();
|
|
3584
|
+
if (target === null) return null;
|
|
3585
|
+
const rel = relative4(resolveWriteTarget(projectDir) ?? projectDir, target);
|
|
3586
|
+
if (rel === "" || isAbsolute(rel)) return null;
|
|
3587
|
+
const slash = rel.split(sep5).join("/");
|
|
3588
|
+
return slash === ".." || slash.startsWith("../") ? null : slash;
|
|
3589
|
+
}
|
|
3590
|
+
function inHarnessSlot(projectDir, path, slots = HARNESS_FILES, opts = {}) {
|
|
3591
|
+
const norm = posix.normalize(path);
|
|
3592
|
+
if (!within(norm, slots)) return false;
|
|
3593
|
+
const real = realRel(projectDir, norm, opts.follow === true);
|
|
3594
|
+
return real !== null && within(real, slots);
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
// src/displaced.ts
|
|
3539
3598
|
function exists(abs) {
|
|
3540
3599
|
try {
|
|
3541
3600
|
lstatSync3(abs);
|
|
@@ -3545,12 +3604,12 @@ function exists(abs) {
|
|
|
3545
3604
|
}
|
|
3546
3605
|
}
|
|
3547
3606
|
function outsideBackup(projectDir, backup) {
|
|
3548
|
-
const abs =
|
|
3607
|
+
const abs = join21(projectDir, backup);
|
|
3549
3608
|
const hit = outsideProjectTarget(projectDir, dirname5(abs));
|
|
3550
|
-
return hit === null ? null :
|
|
3609
|
+
return hit === null ? null : join21(hit.target, basename3(abs));
|
|
3551
3610
|
}
|
|
3552
3611
|
function isBackupOf(path, backup) {
|
|
3553
|
-
return backupOriginal(
|
|
3612
|
+
return backupOriginal(posix2.normalize(backup)) === posix2.normalize(path);
|
|
3554
3613
|
}
|
|
3555
3614
|
function isLegacyDisplaced(log, projectDir, f) {
|
|
3556
3615
|
if (f.change !== "displaced") return false;
|
|
@@ -3558,7 +3617,7 @@ function isLegacyDisplaced(log, projectDir, f) {
|
|
|
3558
3617
|
if (outsideBackup(projectDir, at2) !== null) return false;
|
|
3559
3618
|
let original;
|
|
3560
3619
|
try {
|
|
3561
|
-
original = readFileSync15(
|
|
3620
|
+
original = readFileSync15(join21(projectDir, at2), "utf8");
|
|
3562
3621
|
} catch {
|
|
3563
3622
|
return false;
|
|
3564
3623
|
}
|
|
@@ -3568,8 +3627,18 @@ function restoreDisplaced(log, projectDir, opts) {
|
|
|
3568
3627
|
const out = [];
|
|
3569
3628
|
for (const f of log.rootFiles ?? []) {
|
|
3570
3629
|
if (f.change !== "displaced" || !opts.has(f.path)) continue;
|
|
3571
|
-
const slot =
|
|
3630
|
+
const slot = join21(projectDir, f.path);
|
|
3572
3631
|
if (outsideProjectTarget(projectDir, slot) !== null) continue;
|
|
3632
|
+
if (!inHarnessSlot(projectDir, f.path)) {
|
|
3633
|
+
out.push({
|
|
3634
|
+
path: f.path,
|
|
3635
|
+
backup: f.notes[0] ?? null,
|
|
3636
|
+
verdict: "advise",
|
|
3637
|
+
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
|
|
3639
|
+
});
|
|
3640
|
+
continue;
|
|
3641
|
+
}
|
|
3573
3642
|
const backup = isLegacyDisplaced(log, projectDir, f) ? null : f.notes[0] ?? null;
|
|
3574
3643
|
const away = backup === null ? null : outsideBackup(projectDir, backup);
|
|
3575
3644
|
if (away !== null) {
|
|
@@ -3594,12 +3663,12 @@ function restoreDisplaced(log, projectDir, opts) {
|
|
|
3594
3663
|
}
|
|
3595
3664
|
const d2 = judgeDisplaced({
|
|
3596
3665
|
backup,
|
|
3597
|
-
backupExists: backup !== null && exists(
|
|
3666
|
+
backupExists: backup !== null && exists(join21(projectDir, backup)),
|
|
3598
3667
|
slotEmpty: !exists(slot) || !opts.write && opts.willBeGone?.has(f.path) === true
|
|
3599
3668
|
});
|
|
3600
3669
|
if (opts.write && d2.verdict === "restore" && backup !== null) {
|
|
3601
3670
|
mkdirSync5(dirname5(slot), { recursive: true });
|
|
3602
|
-
renameSync(
|
|
3671
|
+
renameSync(join21(projectDir, backup), slot);
|
|
3603
3672
|
}
|
|
3604
3673
|
out.push({
|
|
3605
3674
|
path: f.path,
|
|
@@ -3619,7 +3688,7 @@ function withoutRestored(rootFiles, outcomes) {
|
|
|
3619
3688
|
// src/excluded-still-there.ts
|
|
3620
3689
|
init_esm_shims();
|
|
3621
3690
|
import { existsSync as existsSync16 } from "fs";
|
|
3622
|
-
import { join as
|
|
3691
|
+
import { join as join22 } from "path";
|
|
3623
3692
|
function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
3624
3693
|
const out = [];
|
|
3625
3694
|
const add = (id, catalog) => {
|
|
@@ -3632,7 +3701,7 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
|
3632
3701
|
for (const id of excluded) {
|
|
3633
3702
|
if (INTERNAL_BUNDLED_SKILL_IDS.includes(id)) {
|
|
3634
3703
|
const there = [".claude/skills", ".agents/skills"].some(
|
|
3635
|
-
(d2) => existsSync16(
|
|
3704
|
+
(d2) => existsSync16(join22(projectDir, d2, id))
|
|
3636
3705
|
);
|
|
3637
3706
|
if (there) add(id, false);
|
|
3638
3707
|
} else {
|
|
@@ -3646,9 +3715,9 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
|
|
|
3646
3715
|
function assetStillThere(projectDir, asset) {
|
|
3647
3716
|
if (asset.method !== "skill" || asset.scope === "global") return true;
|
|
3648
3717
|
if (asset.files !== void 0)
|
|
3649
|
-
return asset.files.some((f) => existsSync16(
|
|
3718
|
+
return asset.files.some((f) => existsSync16(join22(projectDir, f.path)));
|
|
3650
3719
|
const dir = asset.detail.skill ?? asset.id;
|
|
3651
|
-
return [".claude/skills", ".agents/skills"].some((d2) => existsSync16(
|
|
3720
|
+
return [".claude/skills", ".agents/skills"].some((d2) => existsSync16(join22(projectDir, d2, dir)));
|
|
3652
3721
|
}
|
|
3653
3722
|
|
|
3654
3723
|
// src/external-installer.ts
|
|
@@ -3669,7 +3738,7 @@ import {
|
|
|
3669
3738
|
rmSync as rmSync3
|
|
3670
3739
|
} from "fs";
|
|
3671
3740
|
import { homedir as homedir2 } from "os";
|
|
3672
|
-
import { basename as basename4, dirname as dirname6, join as
|
|
3741
|
+
import { basename as basename4, dirname as dirname6, join as join23, relative as relative5, sep as sep6 } from "path";
|
|
3673
3742
|
var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
|
|
3674
3743
|
function skillRootOf(cli) {
|
|
3675
3744
|
return cli === "claude" ? ".claude/skills" : ".agents/skills";
|
|
@@ -3677,11 +3746,11 @@ function skillRootOf(cli) {
|
|
|
3677
3746
|
function snapshotSkillRoots(projectDir) {
|
|
3678
3747
|
const out = /* @__PURE__ */ new Map();
|
|
3679
3748
|
for (const root of SKILL_ROOTS) {
|
|
3680
|
-
if (outsideProjectTarget(projectDir,
|
|
3681
|
-
for (const rel of listFilesRecursive(
|
|
3749
|
+
if (outsideProjectTarget(projectDir, join23(projectDir, root)) !== null) continue;
|
|
3750
|
+
for (const rel of listFilesRecursive(join23(projectDir, root))) {
|
|
3682
3751
|
const path = `${root}/${rel}`;
|
|
3683
3752
|
try {
|
|
3684
|
-
out.set(path, hashContent(readFileSync16(
|
|
3753
|
+
out.set(path, hashContent(readFileSync16(join23(projectDir, path), "utf8")));
|
|
3685
3754
|
} catch {
|
|
3686
3755
|
}
|
|
3687
3756
|
}
|
|
@@ -3699,8 +3768,8 @@ function homeSkillDirs(roots) {
|
|
|
3699
3768
|
}
|
|
3700
3769
|
for (const name of names) {
|
|
3701
3770
|
try {
|
|
3702
|
-
const st2 = lstatSync4(
|
|
3703
|
-
if (st2.isDirectory()) out.set(
|
|
3771
|
+
const st2 = lstatSync4(join23(root, name), { bigint: true });
|
|
3772
|
+
if (st2.isDirectory()) out.set(join23(root, name), `${st2.ino}:${st2.ctimeNs}`);
|
|
3704
3773
|
} catch {
|
|
3705
3774
|
}
|
|
3706
3775
|
}
|
|
@@ -3713,7 +3782,7 @@ function writtenSkillNames(before, after) {
|
|
|
3713
3782
|
}
|
|
3714
3783
|
function lockedSkillNames(source) {
|
|
3715
3784
|
const xdg = process.env.XDG_STATE_HOME;
|
|
3716
|
-
const path = xdg ?
|
|
3785
|
+
const path = xdg ? join23(xdg, "skills", ".skill-lock.json") : join23(homedir2(), ".agents", ".skill-lock.json");
|
|
3717
3786
|
try {
|
|
3718
3787
|
const lock = JSON.parse(readFileSync16(path, "utf8"));
|
|
3719
3788
|
if (typeof lock.version !== "number" || lock.version < 3) return null;
|
|
@@ -3816,7 +3885,7 @@ function installOne(asset, ctx) {
|
|
|
3816
3885
|
return { ...result2, skills: written.filter((n) => locked.has(n)) };
|
|
3817
3886
|
}
|
|
3818
3887
|
const outside = SKILL_ROOTS.flatMap((root) => {
|
|
3819
|
-
const hit = outsideProjectTarget(cwd,
|
|
3888
|
+
const hit = outsideProjectTarget(cwd, join23(cwd, root));
|
|
3820
3889
|
return hit === null ? [] : [{ root, target: hit.target }];
|
|
3821
3890
|
});
|
|
3822
3891
|
const outRoots = new Set(outside.map((o) => o.root));
|
|
@@ -3898,16 +3967,19 @@ function refreshExternalSkills(projectDir, deps = {}) {
|
|
|
3898
3967
|
if (targets.length === 0) {
|
|
3899
3968
|
return { ...none, notInCatalog, unknown: false, ...copies };
|
|
3900
3969
|
}
|
|
3970
|
+
const scopeOf = (a) => resolveScope(log.assets.find((r) => r.id === a.id)?.scope ?? log.scope);
|
|
3901
3971
|
const targetIds = new Set(targets.map((a) => a.id));
|
|
3902
3972
|
const clis = [...installedClis(log)];
|
|
3973
|
+
const inProject = targets.filter((a) => scopeOf(a) === "project");
|
|
3974
|
+
const slotCtx = { log, scope: "project" };
|
|
3903
3975
|
const settle = holdSkillSlots(projectDir, {
|
|
3904
3976
|
recorded: log.assets.filter((a) => targetIds.has(a.id)),
|
|
3905
|
-
slots: skillSlotsFor(
|
|
3977
|
+
slots: skillSlotsFor(inProject, clis, slotCtx),
|
|
3978
|
+
unlisted: unlistedSkillSlots(inProject, clis, slotCtx),
|
|
3906
3979
|
log,
|
|
3907
3980
|
clis,
|
|
3908
|
-
scope:
|
|
3981
|
+
scope: "project"
|
|
3909
3982
|
});
|
|
3910
|
-
const scopeOf = (a) => resolveScope(log.assets.find((r) => r.id === a.id)?.scope ?? log.scope);
|
|
3911
3983
|
const run = deps.run ?? runExternalInstall;
|
|
3912
3984
|
let ran;
|
|
3913
3985
|
try {
|
|
@@ -3975,10 +4047,10 @@ function holdSkillSlots(projectDir, hold) {
|
|
|
3975
4047
|
const root = dirname6(f.slot);
|
|
3976
4048
|
let dir = asides.get(root);
|
|
3977
4049
|
if (dir === void 0) {
|
|
3978
|
-
dir = claimBackupDir(
|
|
4050
|
+
dir = claimBackupDir(join23(projectDir, root), now);
|
|
3979
4051
|
asides.set(root, dir);
|
|
3980
4052
|
}
|
|
3981
|
-
const aside =
|
|
4053
|
+
const aside = join23(dir, f.rel.slice(root.length + 1));
|
|
3982
4054
|
mkdirSync6(dirname6(aside), { recursive: true });
|
|
3983
4055
|
renameSync2(f.abs, aside);
|
|
3984
4056
|
moved.push({ ...f, aside });
|
|
@@ -4001,12 +4073,17 @@ function holdSkillSlots(projectDir, hold) {
|
|
|
4001
4073
|
displaced: [],
|
|
4002
4074
|
judged: []
|
|
4003
4075
|
};
|
|
4076
|
+
const rewritten = new Set(
|
|
4077
|
+
(report?.attempted ?? []).flatMap(
|
|
4078
|
+
(r) => r.ok ? (r.files?.created ?? []).map((x) => skillFolderOf(x.path)) : []
|
|
4079
|
+
)
|
|
4080
|
+
);
|
|
4004
4081
|
const back = /* @__PURE__ */ new Set();
|
|
4005
4082
|
const failed = [];
|
|
4006
4083
|
for (const f of moved) {
|
|
4007
4084
|
const restore = failedSlots === null || failedSlots.some((s) => f.rel === s || f.rel.startsWith(`${s}/`));
|
|
4008
4085
|
try {
|
|
4009
|
-
const r = settleAside(projectDir, f, restore);
|
|
4086
|
+
const r = settleAside(projectDir, f, restore, rewritten.has(skillFolderOf(f.rel)));
|
|
4010
4087
|
if (r === "back") {
|
|
4011
4088
|
back.add(f.rel);
|
|
4012
4089
|
continue;
|
|
@@ -4014,11 +4091,11 @@ function holdSkillSlots(projectDir, hold) {
|
|
|
4014
4091
|
out.backups.push(r.backup);
|
|
4015
4092
|
if (r.displaced !== null) out.displaced.push(r.displaced);
|
|
4016
4093
|
const backup = toProjectRel(projectDir, r.backup);
|
|
4017
|
-
const line = !f.recorded ? LINES.hadFile : r.gone ? LINES.editedRemoved : LINES.edited;
|
|
4094
|
+
const line = f.unlisted ? r.gone ? LINES.noFileListRemoved : LINES.noFileList : !f.recorded ? LINES.hadFile : r.gone ? LINES.editedRemoved : LINES.edited;
|
|
4018
4095
|
out.judged.push({
|
|
4019
4096
|
path: f.rel,
|
|
4020
4097
|
verdict: r.gone ? "backup+remove" : "backup+overwrite",
|
|
4021
|
-
recorded: f.recorded ? "sha" : "none",
|
|
4098
|
+
recorded: f.recorded ? "sha" : f.unlisted ? "no-sha" : "none",
|
|
4022
4099
|
line: withBackup(line, backup),
|
|
4023
4100
|
backup,
|
|
4024
4101
|
backupAbs: r.backup
|
|
@@ -4043,13 +4120,18 @@ function skillSlotsFor(assets, clis, ctx) {
|
|
|
4043
4120
|
const out = /* @__PURE__ */ new Set();
|
|
4044
4121
|
for (const a of assets) {
|
|
4045
4122
|
if (a.method.kind !== "skill") continue;
|
|
4046
|
-
const prior = ctx.log?.assets.find((r) => r.id === a.id);
|
|
4047
|
-
if (prior !== void 0 && prior.files === void 0) continue;
|
|
4048
4123
|
const skill = a.method.skill;
|
|
4049
4124
|
for (const root of roots) out.add(skill === void 0 ? root : `${root}/${skill}`);
|
|
4050
4125
|
}
|
|
4051
4126
|
return [...out];
|
|
4052
4127
|
}
|
|
4128
|
+
function unlistedSkillSlots(assets, clis, ctx) {
|
|
4129
|
+
const unlisted = assets.filter((a) => {
|
|
4130
|
+
const prior = ctx.log?.assets.find((r) => r.id === a.id);
|
|
4131
|
+
return prior !== void 0 && prior.files === void 0;
|
|
4132
|
+
});
|
|
4133
|
+
return skillSlotsFor(unlisted, clis, ctx);
|
|
4134
|
+
}
|
|
4053
4135
|
function reclaimGlobalSkillCopies(projectDir, log, opts) {
|
|
4054
4136
|
const clis = installedClis(log);
|
|
4055
4137
|
const roots = clis.length === 0 ? [...SKILL_ROOTS] : [...new Set(clis.map(skillRootOf))];
|
|
@@ -4062,17 +4144,19 @@ function reclaimGlobalSkillCopies(projectDir, log, opts) {
|
|
|
4062
4144
|
if (a.method !== "skill" || a.scope !== "global" || excluded.has(a.id)) continue;
|
|
4063
4145
|
if (opts.ids !== void 0 && !opts.ids.has(a.id)) continue;
|
|
4064
4146
|
const skill = a.detail.skill;
|
|
4065
|
-
const names = skill !== void 0 ? [skill] :
|
|
4147
|
+
const names = (skill !== void 0 ? [skill] : Array.isArray(a.skills) ? a.skills : []).filter(
|
|
4148
|
+
isSkillName
|
|
4149
|
+
);
|
|
4066
4150
|
for (const name of names) {
|
|
4067
4151
|
for (const root of roots) {
|
|
4068
4152
|
const slot = `${root}/${name}`;
|
|
4069
|
-
const real = realOrNull(
|
|
4153
|
+
const real = realOrNull(join23(projectDir, slot));
|
|
4070
4154
|
if (real !== null) {
|
|
4071
4155
|
if (seen.has(real)) continue;
|
|
4072
4156
|
seen.add(real);
|
|
4073
4157
|
}
|
|
4074
4158
|
const ctx = { log, others, write: opts.write, rm, id: a.id };
|
|
4075
|
-
const r = reclaimCopy(projectDir, slot,
|
|
4159
|
+
const r = reclaimCopy(projectDir, slot, join23(globalSkillRoot(root), name), ctx);
|
|
4076
4160
|
if (r !== null) out.push(r);
|
|
4077
4161
|
}
|
|
4078
4162
|
}
|
|
@@ -4081,15 +4165,15 @@ function reclaimGlobalSkillCopies(projectDir, log, opts) {
|
|
|
4081
4165
|
}
|
|
4082
4166
|
function globalSkillRoot(root) {
|
|
4083
4167
|
if (root === ".claude/skills") {
|
|
4084
|
-
return
|
|
4168
|
+
return join23(process.env.CLAUDE_CONFIG_DIR?.trim() || join23(homedir2(), ".claude"), "skills");
|
|
4085
4169
|
}
|
|
4086
|
-
return
|
|
4170
|
+
return join23(homedir2(), ".agents/skills");
|
|
4087
4171
|
}
|
|
4088
4172
|
function isSkillName(name) {
|
|
4089
4173
|
return typeof name === "string" && name !== "" && name !== "." && name !== ".." && !/[\\/]/.test(name);
|
|
4090
4174
|
}
|
|
4091
4175
|
function reclaimCopy(projectDir, slot, home, ctx) {
|
|
4092
|
-
const abs =
|
|
4176
|
+
const abs = join23(projectDir, slot);
|
|
4093
4177
|
try {
|
|
4094
4178
|
if (lstatSync4(abs, { throwIfNoEntry: false })?.isDirectory() !== true) return null;
|
|
4095
4179
|
if (outsideProjectTarget(projectDir, abs) !== null) return null;
|
|
@@ -4097,8 +4181,15 @@ function reclaimCopy(projectDir, slot, home, ctx) {
|
|
|
4097
4181
|
} catch {
|
|
4098
4182
|
return null;
|
|
4099
4183
|
}
|
|
4100
|
-
const homeExists = existsSync17(home);
|
|
4101
4184
|
const res = { id: ctx.id, slot, removed: [], judged: [], left: [] };
|
|
4185
|
+
if (!inHarnessSlot(projectDir, slot, skillFolders(SKILL_ROOTS))) {
|
|
4186
|
+
res.left.push({
|
|
4187
|
+
path: slot,
|
|
4188
|
+
why: "it leads outside the skill folders this tool installs to \u2014 not touched"
|
|
4189
|
+
});
|
|
4190
|
+
return res;
|
|
4191
|
+
}
|
|
4192
|
+
const homeExists = existsSync17(home);
|
|
4102
4193
|
const now = /* @__PURE__ */ new Date();
|
|
4103
4194
|
let entries;
|
|
4104
4195
|
try {
|
|
@@ -4115,15 +4206,15 @@ function reclaimCopy(projectDir, slot, home, ctx) {
|
|
|
4115
4206
|
res.left.push({ path, why: "a link \u2014 not part of the copy the tool put here" });
|
|
4116
4207
|
continue;
|
|
4117
4208
|
}
|
|
4118
|
-
const file =
|
|
4209
|
+
const file = join23(abs, rel);
|
|
4119
4210
|
try {
|
|
4120
4211
|
const mine = readFileSync16(file);
|
|
4121
|
-
const same = homeExists && readOrNull(
|
|
4212
|
+
const same = homeExists && readOrNull(join23(home, rel))?.equals(mine) === true;
|
|
4122
4213
|
const backupAbs = !same && ctx.write ? backupFile(file, now) : void 0;
|
|
4123
4214
|
if (ctx.write) ctx.rm(file);
|
|
4124
4215
|
res.removed.push(path);
|
|
4125
4216
|
if (same) continue;
|
|
4126
|
-
const line = homeExists ? `not the same as the global copy (${
|
|
4217
|
+
const line = homeExists ? `not the same as the global copy (${join23(home, rel)}) \u2014 saved as <file>.backup-<time>, removed from live` : `no global copy at ${home} to compare \u2014 saved as <file>.backup-<time>, removed from live`;
|
|
4127
4218
|
const judged = {
|
|
4128
4219
|
path,
|
|
4129
4220
|
verdict: "backup+remove",
|
|
@@ -4151,7 +4242,7 @@ function realOrNull(abs) {
|
|
|
4151
4242
|
}
|
|
4152
4243
|
function realFileOrNull(abs) {
|
|
4153
4244
|
const dir = realOrNull(dirname6(abs));
|
|
4154
|
-
return dir === null ? null :
|
|
4245
|
+
return dir === null ? null : join23(dir, basename4(abs));
|
|
4155
4246
|
}
|
|
4156
4247
|
function readOrNull(abs) {
|
|
4157
4248
|
try {
|
|
@@ -4168,7 +4259,7 @@ function slotFilesToKeep(projectDir, hold) {
|
|
|
4168
4259
|
const sha = new Map(mine.map((f) => [f.path, f.sha256]));
|
|
4169
4260
|
const realSha = new Map(
|
|
4170
4261
|
mine.flatMap((f) => {
|
|
4171
|
-
const real = realFileOrNull(
|
|
4262
|
+
const real = realFileOrNull(join23(projectDir, f.path));
|
|
4172
4263
|
return real === null ? [] : [[real, f.sha256]];
|
|
4173
4264
|
})
|
|
4174
4265
|
);
|
|
@@ -4180,17 +4271,23 @@ function slotFilesToKeep(projectDir, hold) {
|
|
|
4180
4271
|
...mine.map((f) => f.path.split("/")).filter((parts) => parts.length > 3).map((parts) => parts.slice(0, 3).join("/")),
|
|
4181
4272
|
...hold.slots
|
|
4182
4273
|
]);
|
|
4274
|
+
const unlisted = new Set(hold.unlisted ?? []);
|
|
4275
|
+
const unlistedAt = (path) => {
|
|
4276
|
+
let owner = "";
|
|
4277
|
+
for (const s of slots) if (path.startsWith(`${s}/`) && s.length > owner.length) owner = s;
|
|
4278
|
+
return unlisted.has(owner);
|
|
4279
|
+
};
|
|
4183
4280
|
const out = /* @__PURE__ */ new Map();
|
|
4184
4281
|
const realProject = realpathSync3(projectDir);
|
|
4185
4282
|
for (const slot of slots) {
|
|
4186
|
-
if (outsideProjectTarget(projectDir,
|
|
4187
|
-
for (const { rel, link } of listEntriesRecursive(
|
|
4283
|
+
if (outsideProjectTarget(projectDir, join23(projectDir, slot)) !== null) continue;
|
|
4284
|
+
for (const { rel, link } of listEntriesRecursive(join23(projectDir, slot))) {
|
|
4188
4285
|
const path = `${slot}/${rel}`;
|
|
4189
4286
|
if (link && SKILL_ROOTS.includes(dirname6(path))) continue;
|
|
4190
|
-
const abs =
|
|
4191
|
-
const real =
|
|
4287
|
+
const abs = join23(projectDir, path);
|
|
4288
|
+
const real = join23(realpathSync3(dirname6(abs)), basename4(abs));
|
|
4192
4289
|
const prev = out.get(real);
|
|
4193
|
-
if (prev !== void 0 && (prev.rel === path || real !==
|
|
4290
|
+
if (prev !== void 0 && (prev.rel === path || real !== join23(realProject, path))) continue;
|
|
4194
4291
|
const recordedSha = sha.get(path) ?? realSha.get(real);
|
|
4195
4292
|
if (recordedSha === void 0 && (others.has(path) || recorded(hold.log, path).state !== "none"))
|
|
4196
4293
|
continue;
|
|
@@ -4203,12 +4300,20 @@ function slotFilesToKeep(projectDir, hold) {
|
|
|
4203
4300
|
} catch {
|
|
4204
4301
|
continue;
|
|
4205
4302
|
}
|
|
4206
|
-
|
|
4303
|
+
const listed = recordedSha !== void 0;
|
|
4304
|
+
out.set(real, {
|
|
4305
|
+
abs,
|
|
4306
|
+
rel: path,
|
|
4307
|
+
slot,
|
|
4308
|
+
recorded: listed,
|
|
4309
|
+
unlisted: !listed && unlistedAt(path) && backupOriginal(path) === null,
|
|
4310
|
+
link
|
|
4311
|
+
});
|
|
4207
4312
|
}
|
|
4208
4313
|
}
|
|
4209
4314
|
return [...out.values()];
|
|
4210
4315
|
}
|
|
4211
|
-
function settleAside(projectDir, f, restore) {
|
|
4316
|
+
function settleAside(projectDir, f, restore, folderRewritten) {
|
|
4212
4317
|
let now = null;
|
|
4213
4318
|
let vacant = false;
|
|
4214
4319
|
try {
|
|
@@ -4219,7 +4324,7 @@ function settleAside(projectDir, f, restore) {
|
|
|
4219
4324
|
if (code !== "ENOENT" && code !== "ENOTDIR") throw e2;
|
|
4220
4325
|
vacant = true;
|
|
4221
4326
|
}
|
|
4222
|
-
const keep = vacant ? f.link || !f.recorded : f.link ? lstatSync4(f.abs).isSymbolicLink() && readlinkSync(f.abs) === readlinkSync(f.aside) : now?.equals(readFileSync16(f.aside)) === true;
|
|
4327
|
+
const keep = vacant ? f.unlisted ? !folderRewritten : f.link || !f.recorded : f.link ? lstatSync4(f.abs).isSymbolicLink() && readlinkSync(f.abs) === readlinkSync(f.aside) : now?.equals(readFileSync16(f.aside)) === true;
|
|
4223
4328
|
if (restore || keep) {
|
|
4224
4329
|
mkdirSync6(dirname6(f.abs), { recursive: true });
|
|
4225
4330
|
renameSync2(f.aside, f.abs);
|
|
@@ -4227,13 +4332,16 @@ function settleAside(projectDir, f, restore) {
|
|
|
4227
4332
|
}
|
|
4228
4333
|
const backup = f.link ? backupLink(f.abs, f.aside) : backupFromCopy(f.abs, f.aside);
|
|
4229
4334
|
rmSync3(f.aside, { force: true });
|
|
4230
|
-
if (f.recorded || vacant) return { backup, gone: vacant, displaced: null };
|
|
4335
|
+
if (f.recorded || f.unlisted || vacant) return { backup, gone: vacant, displaced: null };
|
|
4231
4336
|
return {
|
|
4232
4337
|
backup,
|
|
4233
4338
|
gone: false,
|
|
4234
4339
|
displaced: { path: f.rel, change: "displaced", notes: [toProjectRel(projectDir, backup)] }
|
|
4235
4340
|
};
|
|
4236
4341
|
}
|
|
4342
|
+
function skillFolderOf(path) {
|
|
4343
|
+
return path.split("/").slice(0, 3).join("/");
|
|
4344
|
+
}
|
|
4237
4345
|
function withoutCreated(report, paths) {
|
|
4238
4346
|
if (paths.size === 0) return report;
|
|
4239
4347
|
return {
|
|
@@ -4249,13 +4357,13 @@ function withoutCreated(report, paths) {
|
|
|
4249
4357
|
function pruneEmptyDirs(dir) {
|
|
4250
4358
|
try {
|
|
4251
4359
|
for (const e2 of readdirSync3(dir, { withFileTypes: true }))
|
|
4252
|
-
if (e2.isDirectory()) pruneEmptyDirs(
|
|
4360
|
+
if (e2.isDirectory()) pruneEmptyDirs(join23(dir, e2.name));
|
|
4253
4361
|
rmdirSync(dir);
|
|
4254
4362
|
} catch {
|
|
4255
4363
|
}
|
|
4256
4364
|
}
|
|
4257
4365
|
function toProjectRel(projectDir, abs) {
|
|
4258
|
-
return
|
|
4366
|
+
return relative5(projectDir, abs).split(sep6).join("/");
|
|
4259
4367
|
}
|
|
4260
4368
|
function recordRefreshedFiles(projectDir, log, report, displaced, writeLog) {
|
|
4261
4369
|
const next = new Map(
|
|
@@ -4382,7 +4490,7 @@ function detectVersion(method, spawn, scope, projectDir) {
|
|
|
4382
4490
|
case "plugin":
|
|
4383
4491
|
return detectPluginVersion(method.pluginId, scope, projectDir);
|
|
4384
4492
|
case "npm": {
|
|
4385
|
-
const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) :
|
|
4493
|
+
const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join23(projectDir, "node_modules", method.pkg, "package.json");
|
|
4386
4494
|
if (!pkgJson || !existsSync17(pkgJson)) return void 0;
|
|
4387
4495
|
const parsed = JSON.parse(readFileSync16(pkgJson, "utf8"));
|
|
4388
4496
|
return parsed.version;
|
|
@@ -4396,7 +4504,7 @@ function detectVersion(method, spawn, scope, projectDir) {
|
|
|
4396
4504
|
}
|
|
4397
4505
|
function detectPluginVersion(pluginId, scope, projectDir) {
|
|
4398
4506
|
const claudeScope = scope === "global" ? "user" : "project";
|
|
4399
|
-
const path =
|
|
4507
|
+
const path = join23(homedir2(), ".claude/plugins/installed_plugins.json");
|
|
4400
4508
|
if (!existsSync17(path)) return void 0;
|
|
4401
4509
|
const data = JSON.parse(readFileSync16(path, "utf8"));
|
|
4402
4510
|
const entries = data.plugins?.[pluginId] ?? [];
|
|
@@ -4411,7 +4519,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
|
|
|
4411
4519
|
}
|
|
4412
4520
|
function detectGlobalNpmPackageJsonPath(pkg, spawn) {
|
|
4413
4521
|
const npmRoot = getNpmGlobalRoot(spawn);
|
|
4414
|
-
return npmRoot ?
|
|
4522
|
+
return npmRoot ? join23(npmRoot, pkg, "package.json") : void 0;
|
|
4415
4523
|
}
|
|
4416
4524
|
var npmGlobalRootCache;
|
|
4417
4525
|
function getNpmGlobalRoot(spawn) {
|
|
@@ -4431,7 +4539,7 @@ function getNpmGlobalRoot(spawn) {
|
|
|
4431
4539
|
// src/key-ids.ts
|
|
4432
4540
|
init_esm_shims();
|
|
4433
4541
|
import { readFileSync as readFileSync17 } from "fs";
|
|
4434
|
-
import { join as
|
|
4542
|
+
import { join as join24 } from "path";
|
|
4435
4543
|
function renderedKeyIds(harnessRoot, input, clis) {
|
|
4436
4544
|
const out = /* @__PURE__ */ new Set();
|
|
4437
4545
|
const add = (path, key) => {
|
|
@@ -4454,7 +4562,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
|
|
|
4454
4562
|
for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
|
|
4455
4563
|
for (const key of gitignoreRender().keys()) add(".gitignore", key);
|
|
4456
4564
|
if (clis.includes("claude")) {
|
|
4457
|
-
const template = readFileSync17(
|
|
4565
|
+
const template = readFileSync17(join24(harnessRoot, "templates/settings.json"), "utf8");
|
|
4458
4566
|
for (const key of renderSettingsPortion(template, () => true).keys()) {
|
|
4459
4567
|
add(".claude/settings.json", key);
|
|
4460
4568
|
}
|
|
@@ -4495,7 +4603,7 @@ function withoutAccepts(harnessRoot, spec, previous) {
|
|
|
4495
4603
|
|
|
4496
4604
|
// src/linked-skill-bodies.ts
|
|
4497
4605
|
init_esm_shims();
|
|
4498
|
-
import { join as
|
|
4606
|
+
import { join as join25 } from "path";
|
|
4499
4607
|
function refreshLinkedSkillBodies(params) {
|
|
4500
4608
|
const { harnessRoot, projectDir, ids, baseline, journal } = params;
|
|
4501
4609
|
const bodyKey = (id) => `.agents/skills/${id}/SKILL.md`;
|
|
@@ -4506,7 +4614,7 @@ function refreshLinkedSkillBodies(params) {
|
|
|
4506
4614
|
);
|
|
4507
4615
|
const result = writer.result();
|
|
4508
4616
|
return {
|
|
4509
|
-
updated: ours.filter((id) => written.has(
|
|
4617
|
+
updated: ours.filter((id) => written.has(join25(projectDir, bodyKey(id)))),
|
|
4510
4618
|
notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
|
|
4511
4619
|
files: result.files,
|
|
4512
4620
|
backupPaths: result.backupPaths,
|
|
@@ -4521,7 +4629,7 @@ function refreshLinkedSkillBodies(params) {
|
|
|
4521
4629
|
// src/out-of-track.ts
|
|
4522
4630
|
init_esm_shims();
|
|
4523
4631
|
import { lstatSync as lstatSync5, readFileSync as readFileSync18, rmSync as rmSync4 } from "fs";
|
|
4524
|
-
import { join as
|
|
4632
|
+
import { join as join26 } from "path";
|
|
4525
4633
|
var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
|
|
4526
4634
|
var ANCHOR_RULE = "uzys-harness";
|
|
4527
4635
|
function reclaimOutOfTrack(args) {
|
|
@@ -4541,8 +4649,9 @@ function reclaimOutOfTrack(args) {
|
|
|
4541
4649
|
const name = AGENTS_RULE.exec(f.path)?.[1];
|
|
4542
4650
|
if (name === void 0 || name === ANCHOR_RULE || rendered.has(name)) continue;
|
|
4543
4651
|
if (isBaselineExcluded(`.claude/rules/${name}.md`, args.excluded)) continue;
|
|
4544
|
-
const abs =
|
|
4652
|
+
const abs = join26(projectDir, f.path);
|
|
4545
4653
|
if (args.outside.skip(abs)) continue;
|
|
4654
|
+
if (!inHarnessSlot(projectDir, f.path)) continue;
|
|
4546
4655
|
const stat = lstatSync5(abs, { throwIfNoEntry: false });
|
|
4547
4656
|
if (stat !== void 0 && !stat.isFile()) continue;
|
|
4548
4657
|
if (stat !== void 0 && claudeCopy.has(`rules/${name}.md`)) {
|
|
@@ -4583,14 +4692,14 @@ function tracksBringing(rule) {
|
|
|
4583
4692
|
// src/state.ts
|
|
4584
4693
|
init_esm_shims();
|
|
4585
4694
|
import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
|
|
4586
|
-
import { join as
|
|
4695
|
+
import { join as join27 } from "path";
|
|
4587
4696
|
var META_FILE = ".claude/.installed-tracks";
|
|
4588
4697
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
4589
4698
|
var TRACE_RULES = ["htmx", "nextjs", "data-analysis", "pyside6", "cli-development"].map(
|
|
4590
4699
|
(r) => `.claude/rules/${r}.md`
|
|
4591
4700
|
);
|
|
4592
4701
|
function detectInstallState(projectDir) {
|
|
4593
|
-
const hasClaudeDir = existsSync18(
|
|
4702
|
+
const hasClaudeDir = existsSync18(join27(projectDir, ".claude"));
|
|
4594
4703
|
const read = readInstallLogStatus(projectDir);
|
|
4595
4704
|
if (read.status === "ok") {
|
|
4596
4705
|
return {
|
|
@@ -4610,7 +4719,7 @@ function tracksFromLog(tracks) {
|
|
|
4610
4719
|
return [...new Set(tracks.filter(isTrack))].sort();
|
|
4611
4720
|
}
|
|
4612
4721
|
function collectTraces(projectDir) {
|
|
4613
|
-
const at2 = (rel) =>
|
|
4722
|
+
const at2 = (rel) => join27(projectDir, rel);
|
|
4614
4723
|
const text = (rel) => {
|
|
4615
4724
|
try {
|
|
4616
4725
|
return readFileSync19(at2(rel), "utf8");
|
|
@@ -4684,7 +4793,7 @@ import {
|
|
|
4684
4793
|
unlinkSync as unlinkSync2,
|
|
4685
4794
|
writeFileSync as writeFileSync6
|
|
4686
4795
|
} from "fs";
|
|
4687
|
-
import { basename as basename5, dirname as dirname7, join as
|
|
4796
|
+
import { basename as basename5, dirname as dirname7, join as join28, relative as relative6, sep as sep7 } from "path";
|
|
4688
4797
|
var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
|
|
4689
4798
|
function buildUpdateSpec(projectDir, tracks, only) {
|
|
4690
4799
|
const log = readInstallLog(projectDir);
|
|
@@ -4706,7 +4815,7 @@ function parseUpdateOnly(raw) {
|
|
|
4706
4815
|
return { ok: true, groups: [...new Set(raw)] };
|
|
4707
4816
|
}
|
|
4708
4817
|
function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
4709
|
-
const claudeDir =
|
|
4818
|
+
const claudeDir = join28(projectDir, ".claude");
|
|
4710
4819
|
const startedAt = Date.now();
|
|
4711
4820
|
const backupsAtStart = new Set(
|
|
4712
4821
|
collectRunBackups(projectDir, Number.NEGATIVE_INFINITY).map((b2) => b2.backup)
|
|
@@ -4777,7 +4886,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
4777
4886
|
harnessMcp,
|
|
4778
4887
|
harnessWrote: harness.wrote
|
|
4779
4888
|
});
|
|
4780
|
-
const settingsPath =
|
|
4889
|
+
const settingsPath = join28(claudeDir, "settings.json");
|
|
4781
4890
|
if (claudeManaged && wants("hooks") && existsSync19(settingsPath) && !outside.skip(settingsPath)) {
|
|
4782
4891
|
report.retiredHookRefsRemoved = removeRetiredHookRefs(
|
|
4783
4892
|
settingsPath,
|
|
@@ -4924,7 +5033,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
4924
5033
|
judged.filter((j4) => j4.recorded === "no-sha" && j4.backup !== void 0).map((j4) => j4.path)
|
|
4925
5034
|
);
|
|
4926
5035
|
report.backups = collectRunBackups(projectDir, startedAt, backupsAtStart).map(
|
|
4927
|
-
(b2) => existsSync19(
|
|
5036
|
+
(b2) => existsSync19(join28(projectDir, b2.path)) ? b2 : { ...b2, removed: true }
|
|
4928
5037
|
);
|
|
4929
5038
|
const noChecksum = report.backups.map((b2) => b2.path).filter((p2) => unmeasured.has(p2));
|
|
4930
5039
|
if (noChecksum.length > 0) report.noChecksum = noChecksum;
|
|
@@ -5007,7 +5116,7 @@ function reclaimOutOfTrackRecorded(projectDir, outside) {
|
|
|
5007
5116
|
function deadMcpLeft(projectDir) {
|
|
5008
5117
|
let servers;
|
|
5009
5118
|
try {
|
|
5010
|
-
servers = JSON.parse(readFileSync20(
|
|
5119
|
+
servers = JSON.parse(readFileSync20(join28(projectDir, ".mcp.json"), "utf8")).mcpServers;
|
|
5011
5120
|
} catch {
|
|
5012
5121
|
return [];
|
|
5013
5122
|
}
|
|
@@ -5022,7 +5131,7 @@ function retiredFeaturesPart(projectDir, f) {
|
|
|
5022
5131
|
}
|
|
5023
5132
|
function legacyHookOnDisk(projectDir, rel) {
|
|
5024
5133
|
try {
|
|
5025
|
-
return hasLegacyHarnessHook(readFileSync20(
|
|
5134
|
+
return hasLegacyHarnessHook(readFileSync20(join28(projectDir, rel), "utf8"));
|
|
5026
5135
|
} catch {
|
|
5027
5136
|
return false;
|
|
5028
5137
|
}
|
|
@@ -5048,7 +5157,7 @@ function writeUpdateSharedFiles(args) {
|
|
|
5048
5157
|
excluded: adapterExcluded(harnessRoot, excluded)
|
|
5049
5158
|
});
|
|
5050
5159
|
const tracks = installedTracks(projectDir);
|
|
5051
|
-
const settingsSource =
|
|
5160
|
+
const settingsSource = join28(templatesDir, "settings.json");
|
|
5052
5161
|
if (args.claudeManaged && wants("hooks") && existsSync19(settingsSource)) {
|
|
5053
5162
|
const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
|
|
5054
5163
|
const manifest = buildManifest(spec);
|
|
@@ -5123,8 +5232,8 @@ function catalogRemovedOnRecord(log) {
|
|
|
5123
5232
|
}
|
|
5124
5233
|
function staleSkillDirs(projectDir, claudeDir, clis) {
|
|
5125
5234
|
const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
|
|
5126
|
-
const dirs = [
|
|
5127
|
-
if (clis.some((cli) => cli !== "claude")) dirs.push(
|
|
5235
|
+
const dirs = [join28(claudeDir, "skills")];
|
|
5236
|
+
if (clis.some((cli) => cli !== "claude")) dirs.push(join28(projectDir, ".agents", "skills"));
|
|
5128
5237
|
const ids = /* @__PURE__ */ new Set();
|
|
5129
5238
|
for (const dir of dirs) {
|
|
5130
5239
|
if (!existsSync19(dir)) continue;
|
|
@@ -5135,13 +5244,13 @@ function staleSkillDirs(projectDir, claudeDir, clis) {
|
|
|
5135
5244
|
return [...ids];
|
|
5136
5245
|
}
|
|
5137
5246
|
function staleAgentFiles(claudeDir) {
|
|
5138
|
-
const dir =
|
|
5247
|
+
const dir = join28(claudeDir, "agents");
|
|
5139
5248
|
if (!existsSync19(dir)) return [];
|
|
5140
5249
|
return readdirSync4(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename5(e2.name, ".md")).filter((id) => RETIRED_AGENT_IDS.includes(id));
|
|
5141
5250
|
}
|
|
5142
5251
|
function demotedAgentFiles(claudeDir, tracks) {
|
|
5143
5252
|
if (tracks.length === 0) return [];
|
|
5144
|
-
const dir =
|
|
5253
|
+
const dir = join28(claudeDir, "agents");
|
|
5145
5254
|
if (!existsSync19(dir)) return [];
|
|
5146
5255
|
const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
|
|
5147
5256
|
([id]) => id
|
|
@@ -5157,19 +5266,19 @@ function updateHarnessTargets(projectDir, templatesDir, log, wants, report, fore
|
|
|
5157
5266
|
if (!wants(e2.target.startsWith(".claude/hooks/") ? "hooks" : "rules")) continue;
|
|
5158
5267
|
if (e2.target.startsWith(".claude/") && !claude) continue;
|
|
5159
5268
|
if (isBaselineExcluded(e2.target, excluded)) continue;
|
|
5160
|
-
const source =
|
|
5269
|
+
const source = join28(templatesDir, e2.source);
|
|
5161
5270
|
if (!existsSync19(source)) continue;
|
|
5162
5271
|
const policy = POLICY_DIRS.find((d2) => e2.target.startsWith(`.claude/${d2.dir}/`));
|
|
5163
5272
|
files.push({
|
|
5164
5273
|
path: e2.target,
|
|
5165
5274
|
source,
|
|
5166
5275
|
unit: e2.target,
|
|
5167
|
-
unitExisted: existsSync19(
|
|
5276
|
+
unitExisted: existsSync19(join28(projectDir, e2.target)),
|
|
5168
5277
|
label: policy === void 0 ? e2.target : `.claude/${policy.dir}`
|
|
5169
5278
|
});
|
|
5170
5279
|
}
|
|
5171
5280
|
const refreshedSkills = [];
|
|
5172
|
-
const skillsSource =
|
|
5281
|
+
const skillsSource = join28(templatesDir, "skills");
|
|
5173
5282
|
if (!claude || !(wants("skills") || wants("new-skills")) || !existsSync19(skillsSource)) {
|
|
5174
5283
|
return { files, refreshedSkills };
|
|
5175
5284
|
}
|
|
@@ -5179,20 +5288,20 @@ function updateHarnessTargets(projectDir, templatesDir, log, wants, report, fore
|
|
|
5179
5288
|
for (const d2 of readdirSync4(skillsSource, { withFileTypes: true })) {
|
|
5180
5289
|
if (!d2.isDirectory()) continue;
|
|
5181
5290
|
const unit = `.claude/skills/${d2.name}`;
|
|
5182
|
-
if (occupiedByNonDirectory(
|
|
5291
|
+
if (occupiedByNonDirectory(join28(projectDir, unit))) {
|
|
5183
5292
|
report.skillsSkippedLinks.push(d2.name);
|
|
5184
5293
|
continue;
|
|
5185
5294
|
}
|
|
5186
5295
|
if (!ids.has(d2.name) || excludedSkill(d2.name)) continue;
|
|
5187
|
-
const existed = existsSync19(
|
|
5296
|
+
const existed = existsSync19(join28(projectDir, unit));
|
|
5188
5297
|
if (existed ? !wants("skills") : !wants("new-skills") || !creatable.has(d2.name)) continue;
|
|
5189
|
-
for (const rel of listFilesRecursive(
|
|
5298
|
+
for (const rel of listFilesRecursive(join28(skillsSource, d2.name))) {
|
|
5190
5299
|
const foreign = foreignOwnedTarget(projectDir, `${unit}/${rel}`);
|
|
5191
5300
|
if (foreign !== null) {
|
|
5192
5301
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
5193
5302
|
continue;
|
|
5194
5303
|
}
|
|
5195
|
-
const source =
|
|
5304
|
+
const source = join28(skillsSource, d2.name, rel);
|
|
5196
5305
|
files.push({
|
|
5197
5306
|
path: `${unit}/${rel}`,
|
|
5198
5307
|
source,
|
|
@@ -5230,8 +5339,8 @@ function updateHarnessFiles(args) {
|
|
|
5230
5339
|
report.updated[label] = 0;
|
|
5231
5340
|
const ctx = { prefix: dir, outside, recordOf, projectDir, judged };
|
|
5232
5341
|
report.pruned[label] = pruneOrphans(
|
|
5233
|
-
|
|
5234
|
-
|
|
5342
|
+
join28(projectDir, label),
|
|
5343
|
+
join28(templatesDir, dir),
|
|
5235
5344
|
ext,
|
|
5236
5345
|
ctx,
|
|
5237
5346
|
now
|
|
@@ -5299,7 +5408,7 @@ function updateHarnessFiles(args) {
|
|
|
5299
5408
|
function removedRow(projectDir, path, why, judged, backupAbs) {
|
|
5300
5409
|
const { line, recorded: recorded2 } = judged;
|
|
5301
5410
|
if (backupAbs === null) return { path, verdict: "remove", recorded: recorded2, line: `${why} \u2014 ${line}` };
|
|
5302
|
-
const backup =
|
|
5411
|
+
const backup = relative6(projectDir, backupAbs).split(sep7).join("/");
|
|
5303
5412
|
return {
|
|
5304
5413
|
path,
|
|
5305
5414
|
verdict: "backup+remove",
|
|
@@ -5315,12 +5424,12 @@ function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now,
|
|
|
5315
5424
|
const backedUp = [];
|
|
5316
5425
|
const failed = [];
|
|
5317
5426
|
for (const id of ids) {
|
|
5318
|
-
const targetSkill =
|
|
5319
|
-
const bundled = new Set(listFilesRecursive(
|
|
5427
|
+
const targetSkill = join28(projectDir, ".claude/skills", id);
|
|
5428
|
+
const bundled = new Set(listFilesRecursive(join28(templatesDir, "skills", id)));
|
|
5320
5429
|
for (const rel of listFilesRecursive(targetSkill)) {
|
|
5321
5430
|
if (bundled.has(rel) || basename5(rel).includes(".backup-")) continue;
|
|
5322
5431
|
if (foreignOwnedTarget(projectDir, `.claude/skills/${id}/${rel}`) !== null) continue;
|
|
5323
|
-
const targetFile =
|
|
5432
|
+
const targetFile = join28(targetSkill, rel);
|
|
5324
5433
|
if (outside.skip(targetFile)) continue;
|
|
5325
5434
|
const path = `.claude/skills/${id}/${rel}`;
|
|
5326
5435
|
const rec = recordOf(path);
|
|
@@ -5395,20 +5504,20 @@ function collectRunBackups(projectDir, startedAt, exclude) {
|
|
|
5395
5504
|
const consider = (rel) => {
|
|
5396
5505
|
if (!BACKUP_SUFFIX.test(rel)) return;
|
|
5397
5506
|
if (exclude?.has(rel)) return;
|
|
5398
|
-
const abs =
|
|
5507
|
+
const abs = join28(projectDir, rel);
|
|
5399
5508
|
if (lstatSync6(abs).mtimeMs < startedAt - 1e3) return;
|
|
5400
5509
|
out.push({ path: rel.replace(BACKUP_SUFFIX, ""), backup: rel });
|
|
5401
5510
|
};
|
|
5402
5511
|
for (const e2 of withoutAppleDouble(readdirSync4(projectDir, { withFileTypes: true })))
|
|
5403
5512
|
if (e2.isFile()) consider(e2.name);
|
|
5404
5513
|
for (const dir of BACKUP_SCAN_DIRS) {
|
|
5405
|
-
for (const { rel } of listEntriesRecursive(
|
|
5514
|
+
for (const { rel } of listEntriesRecursive(join28(projectDir, dir))) consider(`${dir}/${rel}`);
|
|
5406
5515
|
}
|
|
5407
5516
|
return out.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
5408
5517
|
}
|
|
5409
5518
|
function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new Set()) {
|
|
5410
|
-
const dir =
|
|
5411
|
-
const file =
|
|
5519
|
+
const dir = join28(projectDir, ".uzys-agent-harness");
|
|
5520
|
+
const file = join28(dir, "update-backups.json");
|
|
5412
5521
|
try {
|
|
5413
5522
|
if (backups.length === 0) {
|
|
5414
5523
|
if (existsSync19(file)) unlinkSync2(file);
|
|
@@ -5440,12 +5549,12 @@ function trackOnlyFileAssets(tracks) {
|
|
|
5440
5549
|
);
|
|
5441
5550
|
}
|
|
5442
5551
|
function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, now = /* @__PURE__ */ new Date()) {
|
|
5443
|
-
if (existsSync19(
|
|
5552
|
+
if (existsSync19(join28(projectDir, LEGACY_ANCHOR_FILE2))) {
|
|
5444
5553
|
report.legacyAnchor = LEGACY_ANCHOR_FILE2;
|
|
5445
5554
|
}
|
|
5446
|
-
const templateMd =
|
|
5555
|
+
const templateMd = join28(templatesDir, "CLAUDE.md");
|
|
5447
5556
|
if (!existsSync19(templateMd)) return;
|
|
5448
|
-
const anchor =
|
|
5557
|
+
const anchor = join28(projectDir, HARNESS_ANCHOR_FILE);
|
|
5449
5558
|
if (outside.skip(anchor)) return;
|
|
5450
5559
|
const existed = existsSync19(anchor);
|
|
5451
5560
|
if (existed) {
|
|
@@ -5456,7 +5565,7 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, no
|
|
|
5456
5565
|
if (!owned) {
|
|
5457
5566
|
const backup = backupFile(anchor, now);
|
|
5458
5567
|
report.anchorBackedUp = true;
|
|
5459
|
-
const rel =
|
|
5568
|
+
const rel = relative6(projectDir, backup).split(sep7).join("/");
|
|
5460
5569
|
const rec = recorded2?.path === HARNESS_ANCHOR_FILE ? { state: "sha", sha256: recorded2.sha256 } : { state: "no-sha" };
|
|
5461
5570
|
const next = readFileSync20(templateMd, "utf8");
|
|
5462
5571
|
const { line } = judge({ op: "write", kind: "harness", rec, disk: current, next });
|
|
@@ -5483,7 +5592,7 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, no
|
|
|
5483
5592
|
recordAnchorBaseline(projectDir, anchor);
|
|
5484
5593
|
}
|
|
5485
5594
|
function upsertRootImport(projectDir, outside) {
|
|
5486
|
-
const target =
|
|
5595
|
+
const target = join28(projectDir, "CLAUDE.md");
|
|
5487
5596
|
if (outside.skip(target)) return false;
|
|
5488
5597
|
const existing = existsSync19(target) ? readFileSync20(target, "utf8") : null;
|
|
5489
5598
|
const next = upsertHarnessImport(existing, {
|
|
@@ -5491,7 +5600,7 @@ function upsertRootImport(projectDir, outside) {
|
|
|
5491
5600
|
// ADR-085 — update 는 선택 목록을 인자로 받지 않는다. 앞 단계(하네스 파일 쓰기)가 지나간 디스크로 판정한다:
|
|
5492
5601
|
// `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
|
|
5493
5602
|
continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
|
|
5494
|
-
(id) => existsSync19(
|
|
5603
|
+
(id) => existsSync19(join28(projectDir, ".claude/skills", id))
|
|
5495
5604
|
)
|
|
5496
5605
|
});
|
|
5497
5606
|
if (next === existing) return false;
|
|
@@ -5526,7 +5635,7 @@ function excludedBaselineOnDisk(projectDir, log) {
|
|
|
5526
5635
|
if (excluded.size === 0) return [];
|
|
5527
5636
|
const spec = buildAssetSpec({ tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS });
|
|
5528
5637
|
return buildManifest(spec).filter(
|
|
5529
|
-
(e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync19(
|
|
5638
|
+
(e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync19(join28(projectDir, e2.target))
|
|
5530
5639
|
).map((e2) => e2.target);
|
|
5531
5640
|
}
|
|
5532
5641
|
function excludedSkillOf(log) {
|
|
@@ -5554,7 +5663,7 @@ function recordedRenderInputs(projectDir, log, tracks = installedTracks(projectD
|
|
|
5554
5663
|
function recordedBundledSkills(projectDir, log) {
|
|
5555
5664
|
if (log === null) {
|
|
5556
5665
|
return INTERNAL_BUNDLED_SKILL_IDS.filter(
|
|
5557
|
-
(id) => existsSync19(
|
|
5666
|
+
(id) => existsSync19(join28(projectDir, ".claude/skills", id)) || existsSync19(join28(projectDir, ".agents/skills", id))
|
|
5558
5667
|
);
|
|
5559
5668
|
}
|
|
5560
5669
|
const ids = recordedSkillIds(projectDir, log);
|
|
@@ -5605,9 +5714,9 @@ var SHARED_SKILLS_DIR = ".agents/skills";
|
|
|
5605
5714
|
var SHARED_SKILL_FILE = /^\.agents\/skills\/([^/]+)\//;
|
|
5606
5715
|
var SHARED_SKILL_MD = /^\.agents\/skills\/([^/]+)\/SKILL\.md$/;
|
|
5607
5716
|
function sharedSkillIdsOnDisk(projectDir) {
|
|
5608
|
-
const root =
|
|
5717
|
+
const root = join28(projectDir, SHARED_SKILLS_DIR);
|
|
5609
5718
|
if (!existsSync19(root)) return /* @__PURE__ */ new Set();
|
|
5610
|
-
return new Set(readdirSync4(root).filter((id) => existsSync19(
|
|
5719
|
+
return new Set(readdirSync4(root).filter((id) => existsSync19(join28(root, id, "SKILL.md"))));
|
|
5611
5720
|
}
|
|
5612
5721
|
var CREATED_SHARED_HOW = {
|
|
5613
5722
|
"opencode.json": "created from template (harness part + template defaults)",
|
|
@@ -5634,14 +5743,14 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp, li
|
|
|
5634
5743
|
const log = readInstallLog(projectDir);
|
|
5635
5744
|
const presentBefore = sharedSkillIdsOnDisk(projectDir);
|
|
5636
5745
|
const rulesBefore = /* @__PURE__ */ new Set();
|
|
5637
|
-
const rulesDir =
|
|
5746
|
+
const rulesDir = join28(projectDir, ".agents", "rules");
|
|
5638
5747
|
if (existsSync19(rulesDir)) {
|
|
5639
5748
|
try {
|
|
5640
5749
|
for (const f of readdirSync4(rulesDir)) rulesBefore.add(f);
|
|
5641
5750
|
} catch {
|
|
5642
5751
|
}
|
|
5643
5752
|
}
|
|
5644
|
-
const agentsMdBefore = existsSync19(
|
|
5753
|
+
const agentsMdBefore = existsSync19(join28(projectDir, "AGENTS.md"));
|
|
5645
5754
|
const result = runCliTransforms({
|
|
5646
5755
|
harnessRoot,
|
|
5647
5756
|
projectDir,
|
|
@@ -5780,7 +5889,7 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp, li
|
|
|
5780
5889
|
],
|
|
5781
5890
|
anchorRetired: result.anchorRetired,
|
|
5782
5891
|
retiredCommands: (result.opencode?.retiredCommands ?? []).map(
|
|
5783
|
-
(p2) =>
|
|
5892
|
+
(p2) => relative6(projectDir, p2).split(sep7).join("/")
|
|
5784
5893
|
),
|
|
5785
5894
|
anchorDisplaced,
|
|
5786
5895
|
agentsMd: agentsMdNews(result.agentsMd, agentsMdBefore, priorPaths),
|
|
@@ -5818,14 +5927,15 @@ function pruneOrphans(target, source, ext, ctx, now) {
|
|
|
5818
5927
|
if (!existsSync19(target) || !existsSync19(source)) return [];
|
|
5819
5928
|
const removed = [];
|
|
5820
5929
|
for (const file of readdirSync4(target)) {
|
|
5821
|
-
if (!file.endsWith(ext) || existsSync19(
|
|
5822
|
-
const targetFile =
|
|
5930
|
+
if (!file.endsWith(ext) || existsSync19(join28(source, file))) continue;
|
|
5931
|
+
const targetFile = join28(target, file);
|
|
5823
5932
|
const path = `.claude/${ctx.prefix}/${file}`;
|
|
5824
5933
|
const rec = ctx.recordOf(path);
|
|
5825
5934
|
try {
|
|
5826
5935
|
const disk = readFileSync20(targetFile, "utf8");
|
|
5827
5936
|
const { verdict, line } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
|
|
5828
5937
|
if (verdict === "leave" || ctx.outside.skip(targetFile)) continue;
|
|
5938
|
+
if (!inHarnessSlot(ctx.projectDir, path, HARNESS_FILES, { follow: true })) continue;
|
|
5829
5939
|
const backupAbs = verdict === "backup+remove" ? backupFile(targetFile, now) : null;
|
|
5830
5940
|
unlinkSync2(targetFile);
|
|
5831
5941
|
removed.push(file);
|
|
@@ -5844,7 +5954,7 @@ function pruneOrphans(target, source, ext, ctx, now) {
|
|
|
5844
5954
|
return removed;
|
|
5845
5955
|
}
|
|
5846
5956
|
function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
5847
|
-
const target =
|
|
5957
|
+
const target = join28(projectDir, ".mcp-allowlist");
|
|
5848
5958
|
if (!existsSync19(target)) return null;
|
|
5849
5959
|
try {
|
|
5850
5960
|
const backup = backupFile(target, now);
|
|
@@ -5866,11 +5976,11 @@ function recordClaudeCommand(log, tracks) {
|
|
|
5866
5976
|
function runInstall(ctx) {
|
|
5867
5977
|
const { harnessRoot, projectDir } = ctx;
|
|
5868
5978
|
const mode = ctx.mode ?? "fresh";
|
|
5869
|
-
const templatesDir =
|
|
5979
|
+
const templatesDir = join29(harnessRoot, "templates");
|
|
5870
5980
|
if (!existsSync20(templatesDir)) {
|
|
5871
5981
|
throw new Error(`Templates dir not found: ${templatesDir}`);
|
|
5872
5982
|
}
|
|
5873
|
-
const claudeDir =
|
|
5983
|
+
const claudeDir = join29(projectDir, ".claude");
|
|
5874
5984
|
const previousLog = readInstallLog(projectDir);
|
|
5875
5985
|
const legacyAnchor = mode === "update" ? null : legacyAnchorTrace(projectDir, ctx.spec);
|
|
5876
5986
|
if (mode === "update" && previousLog === null) {
|
|
@@ -5895,13 +6005,13 @@ function runInstall(ctx) {
|
|
|
5895
6005
|
const manifestSpec = buildManifestSpec(spec);
|
|
5896
6006
|
const baselineExcluded = new Set(spec.baselineExclude ?? []);
|
|
5897
6007
|
if (spec.cli.includes("claude")) {
|
|
5898
|
-
const settingsPath =
|
|
6008
|
+
const settingsPath = join29(projectDir, ".claude", "settings.json");
|
|
5899
6009
|
if (existsSync20(settingsPath)) {
|
|
5900
6010
|
try {
|
|
5901
6011
|
JSON.parse(readFileSync21(settingsPath, "utf8"));
|
|
5902
6012
|
} catch {
|
|
5903
6013
|
throw new Error(
|
|
5904
|
-
`${
|
|
6014
|
+
`${relative7(process.cwd(), settingsPath)} is not valid JSON \u2014 left untouched, so no hooks were wired. Fix or delete that file, then run the install again`
|
|
5905
6015
|
);
|
|
5906
6016
|
}
|
|
5907
6017
|
}
|
|
@@ -6195,7 +6305,7 @@ function releasedThisRun(args) {
|
|
|
6195
6305
|
}
|
|
6196
6306
|
if (id.startsWith(BASELINE_PREFIX)) {
|
|
6197
6307
|
const again2 = manifest.some(
|
|
6198
|
-
(e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync20(
|
|
6308
|
+
(e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync20(join29(projectDir, e2.target))
|
|
6199
6309
|
);
|
|
6200
6310
|
return { id, again: again2, tail: "the harness manages it again" };
|
|
6201
6311
|
}
|
|
@@ -6204,7 +6314,7 @@ function releasedThisRun(args) {
|
|
|
6204
6314
|
options: spec.options,
|
|
6205
6315
|
...spec.userOverride ? { userOverride: spec.userOverride } : {}
|
|
6206
6316
|
});
|
|
6207
|
-
const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync20(
|
|
6317
|
+
const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync20(join29(projectDir, d2, id)));
|
|
6208
6318
|
return {
|
|
6209
6319
|
id,
|
|
6210
6320
|
again,
|
|
@@ -6216,10 +6326,10 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
|
|
|
6216
6326
|
const done = new Set(touched);
|
|
6217
6327
|
return keyExclude.flatMap((id) => {
|
|
6218
6328
|
const path = sharedPathOfKeyId(id);
|
|
6219
|
-
if (path === null || done.has(path) || !existsSync20(
|
|
6329
|
+
if (path === null || done.has(path) || !existsSync20(join29(projectDir, path))) return [];
|
|
6220
6330
|
const name = adapterFor(path);
|
|
6221
6331
|
const keys = [...excludedKeys(path, [id])];
|
|
6222
|
-
const present = name === null ? null : ADAPTERS[name].read(readFileSync21(
|
|
6332
|
+
const present = name === null ? null : ADAPTERS[name].read(readFileSync21(join29(projectDir, path), "utf8"), keys, projectDir);
|
|
6223
6333
|
return present !== null && present.size === 0 ? [] : [{ id, path }];
|
|
6224
6334
|
});
|
|
6225
6335
|
}
|
|
@@ -6263,7 +6373,7 @@ function installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, w
|
|
|
6263
6373
|
continue;
|
|
6264
6374
|
}
|
|
6265
6375
|
if (!isCliNeutralTarget(entry.target)) continue;
|
|
6266
|
-
const source =
|
|
6376
|
+
const source = join29(templatesDir, entry.source);
|
|
6267
6377
|
if (!existsSync20(source)) {
|
|
6268
6378
|
result.skipped += 1;
|
|
6269
6379
|
continue;
|
|
@@ -6284,13 +6394,13 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
6284
6394
|
if (!entry.applies(manifestSpec)) {
|
|
6285
6395
|
continue;
|
|
6286
6396
|
}
|
|
6287
|
-
const target =
|
|
6397
|
+
const target = join29(projectDir, entry.target);
|
|
6288
6398
|
if (isBaselineExcluded(entry.target, baselineExcluded)) {
|
|
6289
6399
|
result.excluded.push(entry.target);
|
|
6290
6400
|
if (existsSync20(target)) result.excludedOnDisk.push(entry.target);
|
|
6291
6401
|
continue;
|
|
6292
6402
|
}
|
|
6293
|
-
const source =
|
|
6403
|
+
const source = join29(templatesDir, entry.source);
|
|
6294
6404
|
if (!existsSync20(source)) {
|
|
6295
6405
|
result.skipped += 1;
|
|
6296
6406
|
continue;
|
|
@@ -6321,18 +6431,18 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
6321
6431
|
if (!result.foreignOwned.includes(slot)) result.foreignOwned.push(slot);
|
|
6322
6432
|
continue;
|
|
6323
6433
|
}
|
|
6324
|
-
if (writer.skipOutside(
|
|
6434
|
+
if (writer.skipOutside(join29(projectDir, entry.target, rel))) {
|
|
6325
6435
|
leftOutside += 1;
|
|
6326
6436
|
continue;
|
|
6327
6437
|
}
|
|
6328
|
-
writer.harness(`${entry.target}/${rel}`, { source:
|
|
6438
|
+
writer.harness(`${entry.target}/${rel}`, { source: join29(source, rel) });
|
|
6329
6439
|
}
|
|
6330
6440
|
if (leftOutside > 0) continue;
|
|
6331
6441
|
result.dirsCopied += 1;
|
|
6332
6442
|
}
|
|
6333
6443
|
accumulateCategory(result.categories, entry);
|
|
6334
6444
|
}
|
|
6335
|
-
const hookDir =
|
|
6445
|
+
const hookDir = join29(projectDir, ".claude/hooks");
|
|
6336
6446
|
if (existsSync20(hookDir)) {
|
|
6337
6447
|
chmodHooksSync(hookDir, projectDir);
|
|
6338
6448
|
}
|
|
@@ -6354,8 +6464,8 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
6354
6464
|
});
|
|
6355
6465
|
result.keptHookRefs.push(
|
|
6356
6466
|
...findStaleHookRefs(
|
|
6357
|
-
|
|
6358
|
-
|
|
6467
|
+
join29(projectDir, ".claude", "settings.json"),
|
|
6468
|
+
join29(projectDir, ".claude")
|
|
6359
6469
|
)
|
|
6360
6470
|
);
|
|
6361
6471
|
}
|
|
@@ -6364,7 +6474,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
6364
6474
|
// ADR-085 · update(`upsertRootImport`)와 같은 판정 — 실제로 깔린 스킬만 안내한다. 밖 링크라 쓰지 않은
|
|
6365
6475
|
// 스킬(ADR-098)을 "installed" 라 적으면 다음 update 가 그 절을 걷어 내 두 동작이 서로 반대로 쓴다.
|
|
6366
6476
|
(manifestSpec.selectedInternalSkills ?? []).filter(
|
|
6367
|
-
(id) => existsSync20(
|
|
6477
|
+
(id) => existsSync20(join29(projectDir, ".claude", "skills", id))
|
|
6368
6478
|
),
|
|
6369
6479
|
harnessRoot,
|
|
6370
6480
|
writer
|
|
@@ -6430,9 +6540,12 @@ function runExternalPhase(ctx, previousLog) {
|
|
|
6430
6540
|
ctx.onProgress?.({ type: "external-start", assetCount: targets.length });
|
|
6431
6541
|
const skills = targets.filter((a) => a.method.kind === "skill");
|
|
6432
6542
|
const skillIds = new Set(skills.map((a) => a.id));
|
|
6543
|
+
const slotCtx = { log: previousLog, scope: spec.scope };
|
|
6433
6544
|
const settle = holdSkillSlots(projectDir, {
|
|
6434
6545
|
recorded: (previousLog?.assets ?? []).filter((a) => a.method === "skill" && skillIds.has(a.id)),
|
|
6435
|
-
slots: skillSlotsFor(skills, spec.cli,
|
|
6546
|
+
slots: skillSlotsFor(skills, spec.cli, slotCtx),
|
|
6547
|
+
// #902 — 옛 판이 파일 목록 없이 깐 자산의 자리 — 그 안 파일은 되살리지 않고 백업만 남긴다
|
|
6548
|
+
unlisted: unlistedSkillSlots(skills, spec.cli, slotCtx),
|
|
6436
6549
|
log: previousLog,
|
|
6437
6550
|
clis: spec.cli,
|
|
6438
6551
|
scope: spec.scope
|
|
@@ -6451,7 +6564,7 @@ function runExternalPhase(ctx, previousLog) {
|
|
|
6451
6564
|
throw e2;
|
|
6452
6565
|
}
|
|
6453
6566
|
const { report: external, backups, displaced } = settle(ran);
|
|
6454
|
-
const displacedBackups = new Set(displaced.map((f) =>
|
|
6567
|
+
const displacedBackups = new Set(displaced.map((f) => join29(projectDir, f.notes[0] ?? "")));
|
|
6455
6568
|
const editBackups = backups.filter((b2) => !displacedBackups.has(b2));
|
|
6456
6569
|
ctx.onProgress?.({
|
|
6457
6570
|
type: "external-complete",
|
|
@@ -6524,7 +6637,7 @@ function recordInterruptedInstall(ctx, error, run) {
|
|
|
6524
6637
|
const codexTrust = registeredTrust(journal.codexOptIn, ctx.projectDir);
|
|
6525
6638
|
const written = writtenSoFar(ledger, cliFiles, rootFiles, stage.rootImportWritten);
|
|
6526
6639
|
if (codexTrust) written.push(`${codexTrust.configPath} (Codex trust entry for this folder)`);
|
|
6527
|
-
const projectRel = (abs) =>
|
|
6640
|
+
const projectRel = (abs) => relative7(ctx.projectDir, abs).split(sep8).join("/");
|
|
6528
6641
|
const writtenSet = new Set(written);
|
|
6529
6642
|
const backups = [...own.backups, ...journal.backups].map((abs) => {
|
|
6530
6643
|
const original = projectRel(backupOriginal(abs) ?? abs);
|
|
@@ -6664,7 +6777,7 @@ function accumulateCategory(cats, entry) {
|
|
|
6664
6777
|
}
|
|
6665
6778
|
}
|
|
6666
6779
|
function writeRootClaudeMd(projectDir, continuousSkills, harnessRoot, writer) {
|
|
6667
|
-
const target =
|
|
6780
|
+
const target = join29(projectDir, "CLAUDE.md");
|
|
6668
6781
|
if (writer.skipOutside(target)) return null;
|
|
6669
6782
|
const existing = existsSync20(target) ? readFileSync21(target, "utf-8") : null;
|
|
6670
6783
|
const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
|
|
@@ -6699,7 +6812,7 @@ function listHookFiles(hookDir) {
|
|
|
6699
6812
|
// src/commands/install-render.ts
|
|
6700
6813
|
init_esm_shims();
|
|
6701
6814
|
import { existsSync as existsSync22, readdirSync as readdirSync6, readFileSync as readFileSync23 } from "fs";
|
|
6702
|
-
import { join as
|
|
6815
|
+
import { join as join31, relative as relative8 } from "path";
|
|
6703
6816
|
|
|
6704
6817
|
// src/preset-recommend.ts
|
|
6705
6818
|
init_esm_shims();
|
|
@@ -6721,16 +6834,21 @@ function trackDefaultAssets(tracks) {
|
|
|
6721
6834
|
])
|
|
6722
6835
|
];
|
|
6723
6836
|
}
|
|
6724
|
-
function finalSelectedAssets(tracks, userOverride) {
|
|
6837
|
+
function finalSelectedAssets(tracks, userOverride, keyExclude = []) {
|
|
6725
6838
|
const selected = new Set(trackDefaultAssets(tracks));
|
|
6726
6839
|
if (userOverride) {
|
|
6727
6840
|
for (const id of userOverride.forceExclude) selected.delete(id);
|
|
6728
6841
|
for (const id of userOverride.forceInclude) selected.add(id);
|
|
6729
6842
|
}
|
|
6843
|
+
if (keyExclude.length > 0) {
|
|
6844
|
+
for (const id of droppedMcpAssetIds(resolveBundleRoot(), new Set(keyExclude))) {
|
|
6845
|
+
selected.delete(id);
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6730
6848
|
return [...selected].sort();
|
|
6731
6849
|
}
|
|
6732
6850
|
function residentSelectedAssets(spec) {
|
|
6733
|
-
return finalSelectedAssets(spec.tracks, spec.userOverride).filter((id) => {
|
|
6851
|
+
return finalSelectedAssets(spec.tracks, spec.userOverride, spec.keyExclude).filter((id) => {
|
|
6734
6852
|
const asset = EXTERNAL_ASSETS.find((a) => a.id === id);
|
|
6735
6853
|
return asset === void 0 || assetReachesCli(asset, spec.cli);
|
|
6736
6854
|
});
|
|
@@ -6750,9 +6868,9 @@ function groupAssetsByCategory(assetIds) {
|
|
|
6750
6868
|
// src/resident-entries.ts
|
|
6751
6869
|
init_esm_shims();
|
|
6752
6870
|
import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
|
|
6753
|
-
import { basename as basename7, join as
|
|
6871
|
+
import { basename as basename7, join as join30 } from "path";
|
|
6754
6872
|
function residentEntries(spec) {
|
|
6755
|
-
const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync21(
|
|
6873
|
+
const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join30(spec.projectDir, d2, id)));
|
|
6756
6874
|
const forceExclude = (spec.userOverride?.forceExclude ?? []).filter((id) => !skillThere(id));
|
|
6757
6875
|
const assetSpec = buildManifestSpec(
|
|
6758
6876
|
spec.userOverride ? { ...spec, userOverride: { ...spec.userOverride, forceExclude } } : spec
|
|
@@ -6778,7 +6896,7 @@ function residentEntries(spec) {
|
|
|
6778
6896
|
function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
|
|
6779
6897
|
if (contextFiles !== void 0) return residentCost(entries, void 0, spec.cli, contextFiles);
|
|
6780
6898
|
const cost = residentCost(entries, void 0, spec.cli, plannedClaudeContext(spec));
|
|
6781
|
-
if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync21(
|
|
6899
|
+
if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync21(join30(spec.projectDir, "AGENTS.md"))) {
|
|
6782
6900
|
return makeResidentCost({
|
|
6783
6901
|
...cost,
|
|
6784
6902
|
contextNote: "planned, not counting text already in AGENTS.md"
|
|
@@ -6789,7 +6907,7 @@ function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
|
|
|
6789
6907
|
function plannedClaudeContext(spec) {
|
|
6790
6908
|
if (!spec.cli.includes("claude")) return void 0;
|
|
6791
6909
|
const root = resolveBundleRoot();
|
|
6792
|
-
const rootClaude =
|
|
6910
|
+
const rootClaude = join30(spec.projectDir, "CLAUDE.md");
|
|
6793
6911
|
const existing = existsSync21(rootClaude) ? readFileSync22(rootClaude, "utf-8") : null;
|
|
6794
6912
|
const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
|
|
6795
6913
|
if (existing === null && seeded === null) return void 0;
|
|
@@ -6799,14 +6917,14 @@ function plannedClaudeContext(spec) {
|
|
|
6799
6917
|
continuousSkills: manifestSpec.selectedInternalSkills ?? [],
|
|
6800
6918
|
...seeded === null ? {} : { projectContext: seeded }
|
|
6801
6919
|
});
|
|
6802
|
-
return [
|
|
6920
|
+
return [join30(root, "templates", "CLAUDE.md"), { text }];
|
|
6803
6921
|
}
|
|
6804
6922
|
function onDiskTargets(spec, assetSpec) {
|
|
6805
6923
|
const out = /* @__PURE__ */ new Set();
|
|
6806
6924
|
for (const e2 of buildManifest(assetSpec)) {
|
|
6807
6925
|
if (!e2.applies(assetSpec)) continue;
|
|
6808
6926
|
const t = classifyBaselineTarget(e2.target);
|
|
6809
|
-
if (t !== null && existsSync21(
|
|
6927
|
+
if (t !== null && existsSync21(join30(spec.projectDir, e2.target))) out.add(t.id);
|
|
6810
6928
|
}
|
|
6811
6929
|
return out;
|
|
6812
6930
|
}
|
|
@@ -6831,7 +6949,7 @@ function renderInstallHeader(log, spec, mode) {
|
|
|
6831
6949
|
);
|
|
6832
6950
|
}
|
|
6833
6951
|
log(infoRow("OPTIONS", formatOptions(spec)));
|
|
6834
|
-
const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride);
|
|
6952
|
+
const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride, spec.keyExclude);
|
|
6835
6953
|
if (finalAssets.length > 0) {
|
|
6836
6954
|
const unreachable = finalAssets.filter((id) => {
|
|
6837
6955
|
const asset = EXTERNAL_ASSETS.find((a) => a.id === id);
|
|
@@ -6993,23 +7111,23 @@ function residentEntriesOnDisk(spec) {
|
|
|
6993
7111
|
const hasClaude = spec.cli.includes("claude");
|
|
6994
7112
|
const surface = anchorSurface(spec.cli).scaffold;
|
|
6995
7113
|
const mdFiles = (dir, kind, skip) => {
|
|
6996
|
-
const abs =
|
|
7114
|
+
const abs = join31(spec.projectDir, dir);
|
|
6997
7115
|
return existsSync22(abs) ? readdirSync6(abs, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md") && e2.name !== skip).map((e2) => ({
|
|
6998
7116
|
source: `${kind}/${e2.name}`,
|
|
6999
7117
|
// `target` 은 `residentCost` 의 분류 키다 — 실제 자리는 `file` 이 말한다.
|
|
7000
7118
|
target: `.claude/${kind}/${e2.name}`,
|
|
7001
7119
|
// 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 본문도,
|
|
7002
7120
|
// 배포판에 더는 없는 파일도 실제로 상주하는 것은 이쪽이다.
|
|
7003
|
-
file:
|
|
7121
|
+
file: join31(abs, e2.name)
|
|
7004
7122
|
})) : [];
|
|
7005
7123
|
};
|
|
7006
|
-
const rules = surface === "claude" ? mdFiles(
|
|
7007
|
-
const slot = hasClaude ?
|
|
7124
|
+
const rules = surface === "claude" ? mdFiles(join31(".claude", "rules"), "rules") : rulesInline(spec.cli) ? planned.filter((e2) => e2.target.startsWith(".claude/rules/")) : mdFiles(join31(".agents", "rules"), "rules", "uzys-harness.md");
|
|
7125
|
+
const slot = hasClaude ? join31(".claude", "skills") : join31(".agents", "skills");
|
|
7008
7126
|
const skills = planned.filter((e2) => e2.target.startsWith(".claude/skills/")).map((e2) => ({
|
|
7009
7127
|
...e2,
|
|
7010
|
-
file:
|
|
7128
|
+
file: join31(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
|
|
7011
7129
|
})).filter((e2) => existsSync22(e2.file));
|
|
7012
|
-
const agents = hasClaude ? mdFiles(
|
|
7130
|
+
const agents = hasClaude ? mdFiles(join31(".claude", "agents"), "agents") : [];
|
|
7013
7131
|
return [
|
|
7014
7132
|
...planned.filter(
|
|
7015
7133
|
(e2) => !e2.target.startsWith(".claude/rules/") && !e2.target.startsWith(".claude/agents/") && !e2.target.startsWith(".claude/skills/")
|
|
@@ -7021,8 +7139,8 @@ function residentEntriesOnDisk(spec) {
|
|
|
7021
7139
|
}
|
|
7022
7140
|
function contextFilesOnDisk(spec) {
|
|
7023
7141
|
const surface = anchorSurface(spec.cli).scaffold;
|
|
7024
|
-
const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md",
|
|
7025
|
-
return files.map((f) =>
|
|
7142
|
+
const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join31(".claude", "CLAUDE.md")] : ["AGENTS.md"];
|
|
7143
|
+
return files.map((f) => join31(spec.projectDir, f));
|
|
7026
7144
|
}
|
|
7027
7145
|
function rulesInsideAgentsMd(spec, cost) {
|
|
7028
7146
|
if (anchorSurface(spec.cli).scaffold !== "agents-md" || !rulesInline(spec.cli)) return cost;
|
|
@@ -7314,7 +7432,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
7314
7432
|
const remote = (report.shared ?? []).find((f) => f.path === ".mcp.json")?.remote ?? [];
|
|
7315
7433
|
for (const line of remoteMcpNextLines(remote, spec.cli)) log(`${" ".repeat(16)} ${line}`);
|
|
7316
7434
|
const scaffoldFiles = scaffoldFilesForCli(spec.cli).filter(
|
|
7317
|
-
(f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(
|
|
7435
|
+
(f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join31(spec.projectDir, f))
|
|
7318
7436
|
);
|
|
7319
7437
|
if (scaffoldFiles.length > 0) {
|
|
7320
7438
|
const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
|
|
@@ -8080,7 +8198,7 @@ function renderInterruptedInstall(err, e2, cwd = process.cwd()) {
|
|
|
8080
8198
|
}
|
|
8081
8199
|
const { path, error } = e2.record;
|
|
8082
8200
|
err(
|
|
8083
|
-
path !== null ? ` Already in place before it stopped \u2014 recorded in ${
|
|
8201
|
+
path !== null ? ` Already in place before it stopped \u2014 recorded in ${relative8(cwd, path) || path}:` : " Already in place before it stopped:"
|
|
8084
8202
|
);
|
|
8085
8203
|
for (const [key, n] of groups) {
|
|
8086
8204
|
err(` ${key.endsWith("/") ? `${key} (${n} file${n === 1 ? "" : "s"})` : key}`);
|
|
@@ -8404,7 +8522,7 @@ function registerInstallCommand(cli) {
|
|
|
8404
8522
|
// src/commands/list.ts
|
|
8405
8523
|
init_esm_shims();
|
|
8406
8524
|
import { existsSync as existsSync23, readFileSync as readFileSync24 } from "fs";
|
|
8407
|
-
import { join as
|
|
8525
|
+
import { join as join32, resolve as resolve3 } from "path";
|
|
8408
8526
|
function listAction(options = {}, deps = {}) {
|
|
8409
8527
|
const log = deps.log ?? console.log;
|
|
8410
8528
|
const err = deps.err ?? console.error;
|
|
@@ -8508,7 +8626,7 @@ function formatHarnessFileRows(log, projectDir) {
|
|
|
8508
8626
|
const rows = [` ${c.dim(tops.length > 0 ? tops.join(" ") : "(none recorded)")}`];
|
|
8509
8627
|
const rootMd = log.templates.rootClaudeMd;
|
|
8510
8628
|
if (rootMd) {
|
|
8511
|
-
const path =
|
|
8629
|
+
const path = join32(projectDir, rootMd.path);
|
|
8512
8630
|
const modified = existsSync23(path) && hashContent(readFileSync24(path, "utf8")) !== rootMd.sha256;
|
|
8513
8631
|
rows.push(
|
|
8514
8632
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
@@ -8523,7 +8641,7 @@ var ROOT_FILE_CHANGE = {
|
|
|
8523
8641
|
displaced: "\uBE44\uCF1C \uB460"
|
|
8524
8642
|
};
|
|
8525
8643
|
function formatRootFileRows(rootFiles, projectDir) {
|
|
8526
|
-
const present = rootFiles.filter((f) => existsSync23(
|
|
8644
|
+
const present = rootFiles.filter((f) => existsSync23(join32(projectDir, f.path)));
|
|
8527
8645
|
const width = Math.max(0, ...present.map((f) => f.path.length));
|
|
8528
8646
|
return present.map(
|
|
8529
8647
|
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(ROOT_FILE_CHANGE[f.change])} ${c.dim(f.notes.join(" / "))}`
|
|
@@ -8554,7 +8672,7 @@ import {
|
|
|
8554
8672
|
statSync as statSync4,
|
|
8555
8673
|
writeFileSync as writeFileSync8
|
|
8556
8674
|
} from "fs";
|
|
8557
|
-
import { dirname as dirname8, join as
|
|
8675
|
+
import { dirname as dirname8, join as join33, relative as relative9, resolve as resolve4, sep as sep9 } from "path";
|
|
8558
8676
|
|
|
8559
8677
|
// src/track-removal.ts
|
|
8560
8678
|
init_esm_shims();
|
|
@@ -10493,7 +10611,7 @@ function unwritablePaths(ctx) {
|
|
|
10493
10611
|
const project = ctx.targetAssets.filter((a) => a.scope === "project");
|
|
10494
10612
|
if (project.some((a) => a.method === "npm")) need.add(".");
|
|
10495
10613
|
const parentOf = (path) => {
|
|
10496
|
-
const abs =
|
|
10614
|
+
const abs = join33(projectDir, path);
|
|
10497
10615
|
if (!safeExists(abs)) return;
|
|
10498
10616
|
const real = safeRealpath(abs);
|
|
10499
10617
|
if (real !== null && isOutsideProject(projectDir, real)) return;
|
|
@@ -10505,7 +10623,7 @@ function unwritablePaths(ctx) {
|
|
|
10505
10623
|
const recordedShared = new Set((installLog.portions ?? []).map((p2) => p2.path));
|
|
10506
10624
|
const made = new Set((installLog.externalFiles ?? []).map((f) => f.path));
|
|
10507
10625
|
for (const p2 of UNINSTALL_SHARED) {
|
|
10508
|
-
if (recordedShared.has(p2) && existsSync24(
|
|
10626
|
+
if (recordedShared.has(p2) && existsSync24(join33(projectDir, p2))) need.add(p2);
|
|
10509
10627
|
if (made.has(p2)) parentOf(p2);
|
|
10510
10628
|
}
|
|
10511
10629
|
for (const p2 of harnessCandidates(installLog)) parentOf(p2);
|
|
@@ -10515,7 +10633,7 @@ function unwritablePaths(ctx) {
|
|
|
10515
10633
|
}
|
|
10516
10634
|
return [...need].filter((p2) => {
|
|
10517
10635
|
try {
|
|
10518
|
-
accessSync2(
|
|
10636
|
+
accessSync2(join33(projectDir, p2), fsConstants2.W_OK);
|
|
10519
10637
|
return false;
|
|
10520
10638
|
} catch (e2) {
|
|
10521
10639
|
return e2.code !== "ENOENT";
|
|
@@ -10618,15 +10736,15 @@ function settleLog(ctx, io) {
|
|
|
10618
10736
|
return true;
|
|
10619
10737
|
}
|
|
10620
10738
|
} else {
|
|
10621
|
-
const recordDir =
|
|
10739
|
+
const recordDir = join33(projectDir, INSTALL_LOG_DIR);
|
|
10622
10740
|
if (outsideLinkAt(projectDir, INSTALL_LOG_DIR) !== null) io.rm(recordDir);
|
|
10623
10741
|
else {
|
|
10624
|
-
for (const rel of RECORD_FILES) io.rm(
|
|
10742
|
+
for (const rel of RECORD_FILES) io.rm(join33(projectDir, rel));
|
|
10625
10743
|
pruneEmptyDirsUpward(projectDir, recordDir);
|
|
10626
10744
|
}
|
|
10627
10745
|
if (outsideLinkAt(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
|
|
10628
10746
|
io.rm(legacyInstallLogPath(projectDir));
|
|
10629
|
-
pruneEmptyDirsUpward(projectDir,
|
|
10747
|
+
pruneEmptyDirsUpward(projectDir, join33(projectDir, LEGACY_INSTALL_LOG_DIR));
|
|
10630
10748
|
}
|
|
10631
10749
|
io.log(` ${status.success("install log removed")}`);
|
|
10632
10750
|
}
|
|
@@ -10730,7 +10848,7 @@ function backupArtifactAdvisoryLines(projectDir) {
|
|
|
10730
10848
|
}
|
|
10731
10849
|
}
|
|
10732
10850
|
for (const dir of BACKUP_SCAN_DIRS) {
|
|
10733
|
-
const abs =
|
|
10851
|
+
const abs = join33(projectDir, dir);
|
|
10734
10852
|
if (!existsSync24(abs) || outsideLinkAt(projectDir, dir) !== null) continue;
|
|
10735
10853
|
for (const rel of listFilesRecursive(abs)) {
|
|
10736
10854
|
if (BACKUP_SUFFIX.test(rel)) found.push(`${dir}/${rel}`);
|
|
@@ -10746,11 +10864,11 @@ function backupArtifactAdvisoryLines(projectDir) {
|
|
|
10746
10864
|
}
|
|
10747
10865
|
function rootFileAdvisoryLines(rootFiles, projectDir) {
|
|
10748
10866
|
const header = "[ROOT] `.claude/` \uBC16\uC5D0 \uB0A8\uB294 \uAC83 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):";
|
|
10749
|
-
const present = rootFiles.filter((f) => existsSync24(
|
|
10867
|
+
const present = rootFiles.filter((f) => existsSync24(join33(projectDir, f.path)));
|
|
10750
10868
|
if (present.length === 0) return [];
|
|
10751
10869
|
const outside = /* @__PURE__ */ new Map();
|
|
10752
10870
|
const inside = present.filter((f) => {
|
|
10753
|
-
const o = outsideProjectTarget(projectDir,
|
|
10871
|
+
const o = outsideProjectTarget(projectDir, join33(projectDir, f.path));
|
|
10754
10872
|
if (o === null) return true;
|
|
10755
10873
|
const group = outside.get(o.link) ?? { link: o, files: [] };
|
|
10756
10874
|
group.files.push(f.path);
|
|
@@ -10789,7 +10907,7 @@ function rootFileMeaning(f, projectDir) {
|
|
|
10789
10907
|
case "displaced": {
|
|
10790
10908
|
const backup = f.notes[0];
|
|
10791
10909
|
if (backup === void 0) return "\uD558\uB124\uC2A4 \uD310\uACFC \uAC19\uC740 \uC124\uCE58\uC790 \uD30C\uC77C\uC774 \uC788\uB358 \uC790\uB9AC (\uADF8\uB300\uB85C \uB450\uC5C8\uB2E4)";
|
|
10792
|
-
return existsSync24(
|
|
10910
|
+
return existsSync24(join33(projectDir, backup)) ? `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC740 ${backup} \uC5D0 \uC788\uB2E4` : `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC758 \uBC31\uC5C5(${backup})\uC774 \uB354\uB294 \uC5C6\uB2E4`;
|
|
10793
10911
|
}
|
|
10794
10912
|
}
|
|
10795
10913
|
}
|
|
@@ -10871,8 +10989,8 @@ function agentsMdRerenderSet(target, remaining, log, projectDir) {
|
|
|
10871
10989
|
const set = agentsMdClis(remaining);
|
|
10872
10990
|
if (set.length === 0) return null;
|
|
10873
10991
|
const recorded2 = (log.externalFiles ?? []).some((f) => f.path === AGENTS_MD2) || (log.portions ?? []).some((p2) => p2.path === AGENTS_MD2);
|
|
10874
|
-
if (!existsSync24(
|
|
10875
|
-
if (!recorded2 && !isLegacyHarnessAgentsMd(readTextOrEmpty(
|
|
10992
|
+
if (!existsSync24(join33(projectDir, AGENTS_MD2))) return null;
|
|
10993
|
+
if (!recorded2 && !isLegacyHarnessAgentsMd(readTextOrEmpty(join33(projectDir, AGENTS_MD2)))) {
|
|
10876
10994
|
return null;
|
|
10877
10995
|
}
|
|
10878
10996
|
return set;
|
|
@@ -11003,9 +11121,9 @@ function runScopedRemoval(ctx, io, plan) {
|
|
|
11003
11121
|
}
|
|
11004
11122
|
for (const line of lines) io.log(line);
|
|
11005
11123
|
const settled = plan.settle(done);
|
|
11006
|
-
const agentsBefore = readTextOrEmpty(
|
|
11124
|
+
const agentsBefore = readTextOrEmpty(join33(projectDir, AGENTS_MD2));
|
|
11007
11125
|
const rerender = plan.rerenderSet === null ? null : rerenderAgentsMd(ctx, plan.rerenderSet, settled);
|
|
11008
|
-
const rewrote = rerender !== null && readTextOrEmpty(
|
|
11126
|
+
const rewrote = rerender !== null && readTextOrEmpty(join33(projectDir, AGENTS_MD2)) !== agentsBefore;
|
|
11009
11127
|
if (rewrote) for (const line of rerenderLines(rerender)) io.log(line);
|
|
11010
11128
|
const next = rerender === null ? settled : withRerender(settled, rerender);
|
|
11011
11129
|
try {
|
|
@@ -11078,7 +11196,7 @@ function removeTrackAction(ctx, io) {
|
|
|
11078
11196
|
}
|
|
11079
11197
|
var TRACK_SCOPE = { shared: [], skeleton: [], folders: [], importBlock: false, anchor: false };
|
|
11080
11198
|
function trackNotes(ctx, r, d2, write) {
|
|
11081
|
-
const md =
|
|
11199
|
+
const md = join33(ctx.projectDir, "CLAUDE.md");
|
|
11082
11200
|
const block = rootBlockNext(ctx, new Set(d2.removed));
|
|
11083
11201
|
if (write && block !== null) {
|
|
11084
11202
|
backupIfLossyUtf8(md);
|
|
@@ -11106,11 +11224,11 @@ function previewTrackRemoval(ctx, target) {
|
|
|
11106
11224
|
}
|
|
11107
11225
|
function rootBlockNext({ installLog, projectDir }, gone) {
|
|
11108
11226
|
const anchor = installLog.templates?.rootClaudeMd?.path;
|
|
11109
|
-
const md =
|
|
11227
|
+
const md = join33(projectDir, "CLAUDE.md");
|
|
11110
11228
|
const text = safeExists(md) && outsideProjectTarget(projectDir, md) === null ? readTextOrEmpty(md) : "";
|
|
11111
|
-
if (anchor === void 0 || !safeExists(
|
|
11229
|
+
if (anchor === void 0 || !safeExists(join33(projectDir, anchor))) return null;
|
|
11112
11230
|
if (stripHarnessImport(text) === null) return null;
|
|
11113
|
-
const stays = (id) => listFilesRecursive(
|
|
11231
|
+
const stays = (id) => listFilesRecursive(join33(projectDir, SKILLS_DIR3, id)).some(
|
|
11114
11232
|
(f) => !gone.has(`${SKILLS_DIR3}${id}/${f}`)
|
|
11115
11233
|
);
|
|
11116
11234
|
const continuousSkills = CONTINUOUS_SKILLS.map((s) => s.id).filter(stays);
|
|
@@ -11121,7 +11239,7 @@ function settleTrackLog(ctx, target, remaining, done) {
|
|
|
11121
11239
|
const { installLog, projectDir } = ctx;
|
|
11122
11240
|
const tracks = installLog.spec.tracks.filter((t) => t !== target);
|
|
11123
11241
|
const next = forgetRemoved({ ...installLog, spec: { ...installLog.spec, tracks } }, done);
|
|
11124
|
-
const abs =
|
|
11242
|
+
const abs = join33(projectDir, INSTALLED_TRACKS);
|
|
11125
11243
|
const key = INSTALLED_TRACKS.slice(CLAUDE_DIR3.length);
|
|
11126
11244
|
const prior = installLog.policyFiles?.find((f) => f.path === key)?.sha256 ?? hashContent(installedTracksText(installLog.spec.tracks.filter(isTrack)));
|
|
11127
11245
|
const edited = safeExists(abs) && hashContent(readTextOrEmpty(abs)) !== prior;
|
|
@@ -11148,7 +11266,7 @@ var SKILLS_LOCK = "skills-lock.json";
|
|
|
11148
11266
|
function withSkillsLock(rootFiles, assets, projectDir) {
|
|
11149
11267
|
const out = [...rootFiles];
|
|
11150
11268
|
const skills = assets.some((a) => a.method === "skill" && a.scope === "project");
|
|
11151
|
-
if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync24(
|
|
11269
|
+
if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync24(join33(projectDir, SKILLS_LOCK))) {
|
|
11152
11270
|
out.push({ path: SKILLS_LOCK, change: "advisory", notes: [] });
|
|
11153
11271
|
}
|
|
11154
11272
|
return out;
|
|
@@ -11159,7 +11277,7 @@ function remnantFor(path, log, harnessRoot, projectDir, wiringGone = []) {
|
|
|
11159
11277
|
const gone = (k2) => wiringGone.includes(`hooks/${k2.split("#")[1] ?? ""}`);
|
|
11160
11278
|
return (disk) => {
|
|
11161
11279
|
try {
|
|
11162
|
-
const template = readFileSync25(
|
|
11280
|
+
const template = readFileSync25(join33(harnessRoot, "templates", "settings.json"), "utf8");
|
|
11163
11281
|
const render = renderSettingsPortion(template, () => true);
|
|
11164
11282
|
return [...legacySettingsSeed(disk, render, projectDir, { edited: true }).keys()].filter(
|
|
11165
11283
|
(k2) => !k2.endsWith("{}") && !gone(k2)
|
|
@@ -11298,7 +11416,7 @@ function noReverseHints(asset, ctx) {
|
|
|
11298
11416
|
}
|
|
11299
11417
|
if (asset.method !== "skill") return [];
|
|
11300
11418
|
const name = asset.detail.skill;
|
|
11301
|
-
const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => existsSync24(
|
|
11419
|
+
const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => existsSync24(join33(ctx.projectDir, p2)));
|
|
11302
11420
|
return [
|
|
11303
11421
|
left.length > 0 ? `files it put down are not on record (installed by an older version) \u2014 left: ${left.join(" \xB7 ")} (delete by hand if the harness installed it)` : "files it put down are not on record (installed by an older version) \u2014 nothing removed"
|
|
11304
11422
|
];
|
|
@@ -11408,6 +11526,7 @@ function removeHarnessFiles(log, projectDir, scope, io) {
|
|
|
11408
11526
|
legacyAgentsMd: [],
|
|
11409
11527
|
unjudged: [],
|
|
11410
11528
|
outside: [],
|
|
11529
|
+
offSlot: [],
|
|
11411
11530
|
displaced: [],
|
|
11412
11531
|
shared: [],
|
|
11413
11532
|
retiredWiring: [],
|
|
@@ -11424,7 +11543,7 @@ function removeHarnessFiles(log, projectDir, scope, io) {
|
|
|
11424
11543
|
}
|
|
11425
11544
|
const judged = new Set(candidates);
|
|
11426
11545
|
for (const path of legacyFileCandidates()) {
|
|
11427
|
-
if (judged.has(path) || !scope.has(path) || !safeExists(
|
|
11546
|
+
if (judged.has(path) || !scope.has(path) || !safeExists(join33(projectDir, path))) continue;
|
|
11428
11547
|
if (recorded(log, path, { isTarget }).state !== "none") continue;
|
|
11429
11548
|
removeRecordedFile(path, { log, projectDir, io, out, isTarget, pruneFrom });
|
|
11430
11549
|
}
|
|
@@ -11450,9 +11569,9 @@ function removeHarnessFiles(log, projectDir, scope, io) {
|
|
|
11450
11569
|
write: io.write
|
|
11451
11570
|
});
|
|
11452
11571
|
if (io.write) {
|
|
11453
|
-
for (const p2 of pruneFrom) pruneEmptyDirsUpward(projectDir, dirname8(
|
|
11572
|
+
for (const p2 of pruneFrom) pruneEmptyDirsUpward(projectDir, dirname8(join33(projectDir, p2)));
|
|
11454
11573
|
for (const d2 of scope.skeleton) {
|
|
11455
|
-
const abs =
|
|
11574
|
+
const abs = join33(projectDir, d2);
|
|
11456
11575
|
if (outsideProjectTarget(projectDir, abs) !== null) continue;
|
|
11457
11576
|
pruneEmptyDirsUpward(projectDir, abs);
|
|
11458
11577
|
}
|
|
@@ -11491,14 +11610,14 @@ function templateNext(harnessRoot, log, path) {
|
|
|
11491
11610
|
if (helper === void 0 && !path.startsWith(CLAUDE_DIR3)) return null;
|
|
11492
11611
|
try {
|
|
11493
11612
|
const source = helper ?? path.slice(CLAUDE_DIR3.length);
|
|
11494
|
-
return readFileSync25(
|
|
11613
|
+
return readFileSync25(join33(harnessRoot, "templates", source), "utf8");
|
|
11495
11614
|
} catch {
|
|
11496
11615
|
return null;
|
|
11497
11616
|
}
|
|
11498
11617
|
}
|
|
11499
11618
|
function removeRecordedFile(path, ctx) {
|
|
11500
11619
|
const { log, projectDir, io, out } = ctx;
|
|
11501
|
-
const abs =
|
|
11620
|
+
const abs = join33(projectDir, path);
|
|
11502
11621
|
let foreign;
|
|
11503
11622
|
try {
|
|
11504
11623
|
foreign = foreignOwnedTarget(projectDir, path);
|
|
@@ -11515,6 +11634,11 @@ function removeRecordedFile(path, ctx) {
|
|
|
11515
11634
|
out.outside.push(outside);
|
|
11516
11635
|
return;
|
|
11517
11636
|
}
|
|
11637
|
+
if (!inHarnessSlot(projectDir, path, HARNESS_FILES, { follow: true })) {
|
|
11638
|
+
out.offSlot.push(path);
|
|
11639
|
+
out.forgotten.add(path);
|
|
11640
|
+
return;
|
|
11641
|
+
}
|
|
11518
11642
|
let st2;
|
|
11519
11643
|
try {
|
|
11520
11644
|
st2 = lstatSync7(abs, { throwIfNoEntry: false });
|
|
@@ -11553,7 +11677,7 @@ function removeRecordedFile(path, ctx) {
|
|
|
11553
11677
|
if (verdict.verdict !== "remove" && verdict.verdict !== "backup+remove") return;
|
|
11554
11678
|
let backup = null;
|
|
11555
11679
|
if (verdict.verdict === "backup+remove") {
|
|
11556
|
-
backup = io.write ?
|
|
11680
|
+
backup = io.write ? relative9(projectDir, backupFile(effective)).split(sep9).join("/") : `${relative9(projectDir, effective).split(sep9).join("/")}.backup-<time>`;
|
|
11557
11681
|
}
|
|
11558
11682
|
if (io.write) {
|
|
11559
11683
|
if (effective !== abs) io.rm(abs);
|
|
@@ -11582,11 +11706,11 @@ function mayReclaimLegacy(log, projectDir, path) {
|
|
|
11582
11706
|
function removeLegacyHookWiring(log, projectDir, scope, io, removed) {
|
|
11583
11707
|
if (!scope.has(SETTINGS_TARGET) || !installedClis(log).includes("claude")) return [];
|
|
11584
11708
|
if ((log.portions ?? []).some((p2) => p2.path === SETTINGS_TARGET)) return [];
|
|
11585
|
-
const abs =
|
|
11709
|
+
const abs = join33(projectDir, SETTINGS_TARGET);
|
|
11586
11710
|
if (!safeExists(abs) || outsideProjectTarget(projectDir, abs) !== null) return [];
|
|
11587
|
-
const claudeDir =
|
|
11711
|
+
const claudeDir = join33(projectDir, ".claude");
|
|
11588
11712
|
const gone = LEGACY_HOOK_REFS.filter(
|
|
11589
|
-
(r) => !RETIRED_HOOK_WIRING_REFS.includes(r) && (removed.includes(`${CLAUDE_DIR3}${r}`) || !safeExists(
|
|
11713
|
+
(r) => !RETIRED_HOOK_WIRING_REFS.includes(r) && (removed.includes(`${CLAUDE_DIR3}${r}`) || !safeExists(join33(claudeDir, r)))
|
|
11590
11714
|
);
|
|
11591
11715
|
return removeRetiredHookRefs(abs, claudeDir, gone, { evenIfScriptExists: true, write: io.write });
|
|
11592
11716
|
}
|
|
@@ -11607,7 +11731,7 @@ function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
|
|
|
11607
11731
|
};
|
|
11608
11732
|
for (const path of paths) {
|
|
11609
11733
|
if (!relevant(path)) continue;
|
|
11610
|
-
const abs =
|
|
11734
|
+
const abs = join33(projectDir, path);
|
|
11611
11735
|
if (safeExists(abs)) {
|
|
11612
11736
|
const o = outsideProjectTarget(projectDir, abs);
|
|
11613
11737
|
if (o !== null) {
|
|
@@ -11618,7 +11742,7 @@ function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
|
|
|
11618
11742
|
if (path === SETTINGS_TARGET && safeExists(abs)) {
|
|
11619
11743
|
out.retiredWiring = removeRetiredHookRefs(
|
|
11620
11744
|
abs,
|
|
11621
|
-
|
|
11745
|
+
join33(projectDir, ".claude"),
|
|
11622
11746
|
RETIRED_HOOK_WIRING_REFS,
|
|
11623
11747
|
{ evenIfScriptExists: true, write: io.write }
|
|
11624
11748
|
);
|
|
@@ -11646,7 +11770,7 @@ function stripSharedFiles(log, projectDir, paths, io, out, pruneFrom) {
|
|
|
11646
11770
|
}
|
|
11647
11771
|
}
|
|
11648
11772
|
function removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom) {
|
|
11649
|
-
const abs =
|
|
11773
|
+
const abs = join33(projectDir, path);
|
|
11650
11774
|
if (!existsSync24(abs)) {
|
|
11651
11775
|
out.forgotten.add(path);
|
|
11652
11776
|
return true;
|
|
@@ -11817,6 +11941,7 @@ function harnessRemovalLines(r, log, projectDir, scope, preview, alsoGone = [])
|
|
|
11817
11941
|
);
|
|
11818
11942
|
}
|
|
11819
11943
|
lines.push(...outsideLines(r.outside, log));
|
|
11944
|
+
lines.push(...offSlotLines(r.offSlot, projectDir));
|
|
11820
11945
|
for (const path of r.kept) {
|
|
11821
11946
|
lines.push(
|
|
11822
11947
|
` ${c.yellow("\u2298")} ${path} kept \u2014 modified since install. Remove manually if intended.`
|
|
@@ -11851,7 +11976,7 @@ function leftFolderLines(r, log, projectDir, scope, preview, alsoGone) {
|
|
|
11851
11976
|
preview ? [...r.removed, ...r.shared.filter((s) => s.fileRemoved).map((s) => s.path), ...alsoGone] : []
|
|
11852
11977
|
);
|
|
11853
11978
|
const back = preview ? r.displaced.filter((d2) => d2.verdict === "restore").map((d2) => d2.path) : [];
|
|
11854
|
-
const said = (full) => r.unjudged.includes(full) || r.kept.includes(full) || r.outside.some((o) => o.path === full || full.startsWith(`${o.link}/`));
|
|
11979
|
+
const said = (full) => r.unjudged.includes(full) || r.kept.includes(full) || r.offSlot.includes(full) || r.outside.some((o) => o.path === full || full.startsWith(`${o.link}/`));
|
|
11855
11980
|
const whose = "not on the install record \u2014 yours, or left by an older harness version; delete by hand any that are not yours";
|
|
11856
11981
|
const lines = [];
|
|
11857
11982
|
for (const dir of scope.folders) {
|
|
@@ -11884,19 +12009,19 @@ function yoursUnder(projectDir, dir) {
|
|
|
11884
12009
|
}
|
|
11885
12010
|
for (const e2 of entries) {
|
|
11886
12011
|
const name = `${prefix}${e2.name}`;
|
|
11887
|
-
if (e2.isDirectory()) walk(
|
|
12012
|
+
if (e2.isDirectory()) walk(join33(abs2, e2.name), `${name}/`);
|
|
11888
12013
|
else if (BACKUP_SUFFIX.test(e2.name)) continue;
|
|
11889
12014
|
else if (e2.isSymbolicLink()) {
|
|
11890
12015
|
let target = "?";
|
|
11891
12016
|
try {
|
|
11892
|
-
target = readlinkSync2(
|
|
12017
|
+
target = readlinkSync2(join33(abs2, e2.name));
|
|
11893
12018
|
} catch {
|
|
11894
12019
|
}
|
|
11895
12020
|
out.push({ name, shown: `${name} (your link \u2192 ${target})` });
|
|
11896
12021
|
} else out.push({ name, shown: name });
|
|
11897
12022
|
}
|
|
11898
12023
|
};
|
|
11899
|
-
const abs =
|
|
12024
|
+
const abs = join33(projectDir, dir);
|
|
11900
12025
|
try {
|
|
11901
12026
|
if (!statSync4(abs).isDirectory()) return [];
|
|
11902
12027
|
} catch {
|
|
@@ -11908,7 +12033,7 @@ function yoursUnder(projectDir, dir) {
|
|
|
11908
12033
|
function assetsLeftLines(log, projectDir, scope) {
|
|
11909
12034
|
const lines = [];
|
|
11910
12035
|
for (const a of log.assets) {
|
|
11911
|
-
const here = (a.files ?? []).map((f) => f.path).filter((p2) => scope.has(p2) && safeExists(
|
|
12036
|
+
const here = (a.files ?? []).map((f) => f.path).filter((p2) => scope.has(p2) && safeExists(join33(projectDir, p2)));
|
|
11912
12037
|
if (here.length === 0) continue;
|
|
11913
12038
|
const slots = [...new Set(here.map((p2) => p2.split("/").slice(0, 3).join("/")))].sort();
|
|
11914
12039
|
lines.push(
|
|
@@ -11920,10 +12045,13 @@ function assetsLeftLines(log, projectDir, scope) {
|
|
|
11920
12045
|
function removeAnchor(log, projectDir, io) {
|
|
11921
12046
|
const rootMd = log.templates?.rootClaudeMd;
|
|
11922
12047
|
if (!rootMd) return { removed: false, lines: [] };
|
|
11923
|
-
const abs =
|
|
12048
|
+
const abs = join33(projectDir, rootMd.path);
|
|
11924
12049
|
if (!existsSync24(abs)) return { removed: false, lines: [] };
|
|
11925
12050
|
const outside = outsideProjectTarget(projectDir, abs);
|
|
11926
12051
|
if (outside !== null) return { removed: false, lines: outsideLines([outside], log) };
|
|
12052
|
+
if (!inHarnessSlot(projectDir, rootMd.path, ANCHOR_FILES, { follow: true })) {
|
|
12053
|
+
return { removed: false, lines: [offSlotLine(rootMd.path, "names it as the harness anchor")] };
|
|
12054
|
+
}
|
|
11927
12055
|
let disk;
|
|
11928
12056
|
try {
|
|
11929
12057
|
disk = readFileSync25(abs, "utf8");
|
|
@@ -11959,7 +12087,7 @@ function removeAnchor(log, projectDir, io) {
|
|
|
11959
12087
|
]
|
|
11960
12088
|
};
|
|
11961
12089
|
}
|
|
11962
|
-
const backup = edited ?
|
|
12090
|
+
const backup = edited ? relative9(projectDir, backupFile(abs)).split(sep9).join("/") : null;
|
|
11963
12091
|
io.rm(abs);
|
|
11964
12092
|
return {
|
|
11965
12093
|
removed: true,
|
|
@@ -11971,7 +12099,7 @@ function removeAnchor(log, projectDir, io) {
|
|
|
11971
12099
|
var AGENTS_MD2 = "AGENTS.md";
|
|
11972
12100
|
function readAgentsMdTemplate(harnessRoot) {
|
|
11973
12101
|
try {
|
|
11974
|
-
return readFileSync25(
|
|
12102
|
+
return readFileSync25(join33(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
|
|
11975
12103
|
} catch {
|
|
11976
12104
|
return null;
|
|
11977
12105
|
}
|
|
@@ -11997,7 +12125,7 @@ function agentsMdRemainder(current, harnessRoot) {
|
|
|
11997
12125
|
return { ok: true, remainder: strip.remainder, strip };
|
|
11998
12126
|
}
|
|
11999
12127
|
function removeUnrecordedLegacyAgentsMd(path, projectDir, io, out, pruneFrom) {
|
|
12000
|
-
const abs =
|
|
12128
|
+
const abs = join33(projectDir, path);
|
|
12001
12129
|
let effective = abs;
|
|
12002
12130
|
let current;
|
|
12003
12131
|
try {
|
|
@@ -12022,7 +12150,7 @@ function removeLegacyAgentsMd(path, abs, effective, strip, ctx) {
|
|
|
12022
12150
|
let backup = `${path}.backup-<time>`;
|
|
12023
12151
|
try {
|
|
12024
12152
|
if (io.write) {
|
|
12025
|
-
backup =
|
|
12153
|
+
backup = relative9(projectDir, backupFile(effective)).split(sep9).join("/");
|
|
12026
12154
|
if (strip.remainder !== null) {
|
|
12027
12155
|
writeFileSync8(effective, neutralizeScaffoldBanner(strip.remainder), "utf8");
|
|
12028
12156
|
} else {
|
|
@@ -12061,14 +12189,52 @@ function globalCopyLines(copies, preview) {
|
|
|
12061
12189
|
];
|
|
12062
12190
|
});
|
|
12063
12191
|
}
|
|
12192
|
+
var ASSET_SLOTS = skillFolders(SKILL_ROOTS);
|
|
12193
|
+
function offSlotLines(paths, projectDir) {
|
|
12194
|
+
const lines = [];
|
|
12195
|
+
const byLink = /* @__PURE__ */ new Map();
|
|
12196
|
+
for (const path of paths) {
|
|
12197
|
+
const link = linkOnPath(projectDir, path);
|
|
12198
|
+
if (link === null) {
|
|
12199
|
+
lines.push(offSlotLine(path, "lists it"));
|
|
12200
|
+
continue;
|
|
12201
|
+
}
|
|
12202
|
+
const g = byLink.get(link.link) ?? { target: link.target, n: 0 };
|
|
12203
|
+
g.n += 1;
|
|
12204
|
+
byLink.set(link.link, g);
|
|
12205
|
+
}
|
|
12206
|
+
for (const [link, g] of byLink) {
|
|
12207
|
+
lines.push(
|
|
12208
|
+
` ${c.yellow("\u2298")} ${link} \u2014 your link (\u2192 ${g.target}) leads outside the places this tool installs to; left ${g.n} file(s) on the install record under it. Not touched \u2014 remove by hand if intended.`
|
|
12209
|
+
);
|
|
12210
|
+
}
|
|
12211
|
+
return lines;
|
|
12212
|
+
}
|
|
12213
|
+
function linkOnPath(projectDir, path) {
|
|
12214
|
+
const parts = path.split("/");
|
|
12215
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
12216
|
+
const rel = parts.slice(0, i).join("/");
|
|
12217
|
+
try {
|
|
12218
|
+
if (lstatSync7(join33(projectDir, rel), { throwIfNoEntry: false })?.isSymbolicLink() === true) {
|
|
12219
|
+
return { link: rel, target: readlinkSync2(join33(projectDir, rel)) };
|
|
12220
|
+
}
|
|
12221
|
+
} catch {
|
|
12222
|
+
return null;
|
|
12223
|
+
}
|
|
12224
|
+
}
|
|
12225
|
+
return null;
|
|
12226
|
+
}
|
|
12227
|
+
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.`;
|
|
12229
|
+
}
|
|
12064
12230
|
function removeToolFiles(files, projectDir, rm) {
|
|
12065
12231
|
const notes = [];
|
|
12066
12232
|
const removed = [];
|
|
12067
12233
|
let failed = 0;
|
|
12068
|
-
const pairs = new Set(files.filter((f) => isAppleDoublePath(
|
|
12234
|
+
const pairs = new Set(files.filter((f) => isAppleDoublePath(join33(projectDir, f.path))));
|
|
12069
12235
|
for (const file of files) {
|
|
12070
12236
|
const { path, sha256 } = file;
|
|
12071
|
-
const abs =
|
|
12237
|
+
const abs = join33(projectDir, path);
|
|
12072
12238
|
if (pairs.has(file)) {
|
|
12073
12239
|
try {
|
|
12074
12240
|
rm(abs);
|
|
@@ -12091,10 +12257,16 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
12091
12257
|
notes.push(`left ${path} \u2014 it is a link now, not the copy that was installed`);
|
|
12092
12258
|
continue;
|
|
12093
12259
|
}
|
|
12260
|
+
if (!inHarnessSlot(projectDir, path, ASSET_SLOTS)) {
|
|
12261
|
+
notes.push(
|
|
12262
|
+
`left ${path} \u2014 the install record lists it for this asset, but it is (or leads) outside the skill folders this asset installs to`
|
|
12263
|
+
);
|
|
12264
|
+
continue;
|
|
12265
|
+
}
|
|
12094
12266
|
try {
|
|
12095
12267
|
if (hashContent(readFileSync25(abs, "utf8")) !== sha256) {
|
|
12096
12268
|
const backup = backupFile(abs);
|
|
12097
|
-
notes.push(`backed up ${path} \u2192 ${
|
|
12269
|
+
notes.push(`backed up ${path} \u2192 ${relative9(projectDir, backup)} (changed since install)`);
|
|
12098
12270
|
}
|
|
12099
12271
|
rm(abs);
|
|
12100
12272
|
removed.push(path);
|
|
@@ -12103,14 +12275,14 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
12103
12275
|
notes.push(`left ${path} \u2014 ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
12104
12276
|
}
|
|
12105
12277
|
}
|
|
12106
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname8(
|
|
12278
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname8(join33(projectDir, path)));
|
|
12107
12279
|
return failed > 0 ? { ok: false, message: `${failed} file(s) could not be removed`, notes } : { ok: true, notes };
|
|
12108
12280
|
}
|
|
12109
12281
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
12110
12282
|
let cursor = startDir;
|
|
12111
12283
|
while (true) {
|
|
12112
|
-
const rel =
|
|
12113
|
-
if (rel === "" || rel.startsWith(`..${
|
|
12284
|
+
const rel = relative9(projectDir, cursor);
|
|
12285
|
+
if (rel === "" || rel.startsWith(`..${sep9}`) || rel === "..") return;
|
|
12114
12286
|
try {
|
|
12115
12287
|
if (readdirSync7(cursor).length > 0) return;
|
|
12116
12288
|
rmdirSync3(cursor);
|
|
@@ -12127,8 +12299,8 @@ function stripRootImport(projectDir) {
|
|
|
12127
12299
|
if (withoutImport === null) return false;
|
|
12128
12300
|
const stripped = neutralizeScaffoldBanner(withoutImport);
|
|
12129
12301
|
try {
|
|
12130
|
-
backupIfLossyUtf8(
|
|
12131
|
-
writeFileSync8(
|
|
12302
|
+
backupIfLossyUtf8(join33(projectDir, "CLAUDE.md"));
|
|
12303
|
+
writeFileSync8(join33(projectDir, "CLAUDE.md"), stripped, "utf8");
|
|
12132
12304
|
return true;
|
|
12133
12305
|
} catch {
|
|
12134
12306
|
return false;
|
|
@@ -12150,10 +12322,10 @@ function importOutsideLine(o, anchorGone) {
|
|
|
12150
12322
|
return ` ${c.yellow("\u2298")} CLAUDE.md kept \u2014 link target is outside the project (${o.target}). Not touched \u2014 its harness @import block (${HARNESS_IMPORT_LINE}) is left in it${pointing} \u2014 delete that block by hand.`;
|
|
12151
12323
|
}
|
|
12152
12324
|
function rootClaudeMdOutside(projectDir) {
|
|
12153
|
-
return outsideProjectTarget(projectDir,
|
|
12325
|
+
return outsideProjectTarget(projectDir, join33(projectDir, "CLAUDE.md"));
|
|
12154
12326
|
}
|
|
12155
12327
|
function readRootClaudeMd(projectDir) {
|
|
12156
|
-
const path =
|
|
12328
|
+
const path = join33(projectDir, "CLAUDE.md");
|
|
12157
12329
|
if (!existsSync24(path)) return null;
|
|
12158
12330
|
try {
|
|
12159
12331
|
return readFileSync25(path, "utf8");
|
|
@@ -12351,7 +12523,8 @@ function classifyUpdateIntent(log, confirmed, legacyTracks = []) {
|
|
|
12351
12523
|
const recordClis = recordedClis(log);
|
|
12352
12524
|
if (!sameSet(confirmed.tracks, recordTracks)) return "add";
|
|
12353
12525
|
if (!sameSet(confirmed.cli, recordClis)) return "add";
|
|
12354
|
-
const
|
|
12526
|
+
const recordedOut = excludedIds(log);
|
|
12527
|
+
const out = /* @__PURE__ */ new Set([...recordedOut, ...droppedMcpAssetIds(resolveBundleRoot(), recordedOut)]);
|
|
12355
12528
|
const recorded2 = log?.assets ?? [];
|
|
12356
12529
|
const covered = coveredByRecord(log, legacyTracks);
|
|
12357
12530
|
const picked = new Set(confirmed.assetIds);
|
|
@@ -12699,7 +12872,8 @@ async function confirmUpdate(input) {
|
|
|
12699
12872
|
function unchecked(log, tracks, assetIds, baselineIds) {
|
|
12700
12873
|
const checked = /* @__PURE__ */ new Set([...assetIds, ...baselineIds]);
|
|
12701
12874
|
const offered = new Set(listBaselineTargets({ tracks }).map((t) => t.id));
|
|
12702
|
-
|
|
12875
|
+
const rechecked = (id) => isKeyId(id) && droppedMcpAssetIds(resolveBundleRoot(), /* @__PURE__ */ new Set([id])).some((a) => checked.has(a));
|
|
12876
|
+
return [...excludedIds(log)].filter((id) => !checked.has(id) && !rechecked(id)).filter((id) => !id.startsWith(BASELINE_PREFIX) || offered.has(id)).sort();
|
|
12703
12877
|
}
|
|
12704
12878
|
function annotate(summary, notes) {
|
|
12705
12879
|
return summary.split("\n").map((line) => {
|
|
@@ -12734,7 +12908,7 @@ function formatSummary(spec) {
|
|
|
12734
12908
|
`CLI: ${spec.cli.join(" \xB7 ")}`,
|
|
12735
12909
|
`Target: ${spec.projectDir}`
|
|
12736
12910
|
];
|
|
12737
|
-
const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride);
|
|
12911
|
+
const finalAssets = finalSelectedAssets(spec.tracks, spec.userOverride, spec.keyExclude);
|
|
12738
12912
|
if (finalAssets.length > 0) {
|
|
12739
12913
|
const unreachable = finalAssets.filter((id) => {
|
|
12740
12914
|
const asset = EXTERNAL_ASSETS.find((a) => a.id === id);
|