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