@uzysjung/agent-harness 26.172.0 → 26.174.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
|
@@ -96,12 +96,13 @@ import {
|
|
|
96
96
|
resolveScope,
|
|
97
97
|
scaffoldBanner,
|
|
98
98
|
sharedPathOfKeyId,
|
|
99
|
+
shouldInstallAsset,
|
|
99
100
|
stripHarnessImport,
|
|
100
101
|
summarizeContextCost,
|
|
101
102
|
targetsInclude,
|
|
102
103
|
upsertHarnessImport,
|
|
103
104
|
writeInstallLog
|
|
104
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-LAWKDFKZ.js";
|
|
105
106
|
|
|
106
107
|
// node_modules/sisteransi/src/index.js
|
|
107
108
|
var require_src = __commonJS({
|
|
@@ -3713,8 +3714,8 @@ function padDisplay(s, width) {
|
|
|
3713
3714
|
|
|
3714
3715
|
// src/install-writes.ts
|
|
3715
3716
|
init_esm_shims();
|
|
3716
|
-
import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
3717
|
-
import { dirname, join as
|
|
3717
|
+
import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3718
|
+
import { dirname, join as join8, relative, sep } from "path";
|
|
3718
3719
|
|
|
3719
3720
|
// src/env-files.ts
|
|
3720
3721
|
init_esm_shims();
|
|
@@ -3804,7 +3805,8 @@ var LINES = {
|
|
|
3804
3805
|
newInRelease: "wrote (new in this release)",
|
|
3805
3806
|
refreshed: "refreshed",
|
|
3806
3807
|
sameAsHarness: "kept \u2014 already the same as the harness version (left yours in place)",
|
|
3807
|
-
|
|
3808
|
+
// 비켜 둔 설치자 파일은 하네스가 그 자리를 떠날 때(uninstall · 회수) 되돌린다(`displaced` · #782) — 그 약속을 같은 줄에
|
|
3809
|
+
hadFile: "had a file with this name \u2014 saved as <file>.backup-<time> \u2014 yours, put back on uninstall",
|
|
3808
3810
|
noChecksum: "no checksum on record \u2014 saved a copy once",
|
|
3809
3811
|
edited: "you edited it \u2014 saved as <file>.backup-<time>",
|
|
3810
3812
|
removed: "removed",
|
|
@@ -3817,6 +3819,9 @@ var LINES = {
|
|
|
3817
3819
|
scaffoldKept: "kept \u2014 already there (yours)",
|
|
3818
3820
|
leftForYou: "left for you (yours now)"
|
|
3819
3821
|
};
|
|
3822
|
+
function withBackup(line, backup) {
|
|
3823
|
+
return line.replace("<file>.backup-<time>", backup);
|
|
3824
|
+
}
|
|
3820
3825
|
function unreadable(adapter, op) {
|
|
3821
3826
|
const what = op === "write" ? "harness part not added" : "harness part not removed";
|
|
3822
3827
|
return `could not read it (${ADAPTERS[adapter].unreadable}) \u2014 ${what}`;
|
|
@@ -3935,7 +3940,8 @@ var PRIOR_MCP_SERVERS = [
|
|
|
3935
3940
|
// #766 — `@supabase/mcp-server` 는 npm 에 없는 이름이라(404) 서버가 뜬 적이 없다
|
|
3936
3941
|
name: "supabase",
|
|
3937
3942
|
value: { type: "stdio", command: "npx", args: ["-y", "@supabase/mcp-server"] },
|
|
3938
|
-
updated: "now Supabase's remote server \u2014 sign in on first use, see NEXT"
|
|
3943
|
+
updated: "now Supabase's remote server \u2014 sign in on first use, see NEXT",
|
|
3944
|
+
dead: `runs @supabase/mcp-server, which is not on npm \u2014 this server never started. To use Supabase's official remote server, set it to { "type": "http", "url": "https://mcp.supabase.com/mcp" } (signs in on first use)`
|
|
3939
3945
|
},
|
|
3940
3946
|
{
|
|
3941
3947
|
name: "chrome-devtools",
|
|
@@ -4116,7 +4122,7 @@ function recordedMcpAssetIds(log) {
|
|
|
4116
4122
|
// src/recorded.ts
|
|
4117
4123
|
init_esm_shims();
|
|
4118
4124
|
import { existsSync as existsSync5 } from "fs";
|
|
4119
|
-
import { join as
|
|
4125
|
+
import { join as join7 } from "path";
|
|
4120
4126
|
|
|
4121
4127
|
// src/cli-ownership.ts
|
|
4122
4128
|
init_esm_shims();
|
|
@@ -4171,6 +4177,42 @@ function removableFor(cli, remaining) {
|
|
|
4171
4177
|
return { exclusive, sharedNowUnowned };
|
|
4172
4178
|
}
|
|
4173
4179
|
|
|
4180
|
+
// src/foreign-slot.ts
|
|
4181
|
+
init_esm_shims();
|
|
4182
|
+
import { lstatSync, realpathSync, statSync } from "fs";
|
|
4183
|
+
import { join as join6 } from "path";
|
|
4184
|
+
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
4185
|
+
function occupiedByNonDirectory(path) {
|
|
4186
|
+
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
4187
|
+
return stat !== void 0 && !stat.isDirectory();
|
|
4188
|
+
}
|
|
4189
|
+
function linksToProjectSharedSkill(projectDir, id) {
|
|
4190
|
+
const slot = join6(projectDir, ".claude/skills", id);
|
|
4191
|
+
if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
|
|
4192
|
+
try {
|
|
4193
|
+
const real = realpathSync(slot);
|
|
4194
|
+
return real === join6(realpathSync(projectDir), ".agents/skills", id) && statSync(real).isDirectory();
|
|
4195
|
+
} catch {
|
|
4196
|
+
return false;
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
function foreignOwnedTarget(projectDir, rel) {
|
|
4200
|
+
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
4201
|
+
if (slot === void 0) return null;
|
|
4202
|
+
if (occupiedByNonDirectory(join6(projectDir, slot))) return slot;
|
|
4203
|
+
if (rel !== slot) {
|
|
4204
|
+
const parts = rel.slice(slot.length + 1).split("/");
|
|
4205
|
+
let cursor = slot;
|
|
4206
|
+
for (const part of parts.slice(0, -1)) {
|
|
4207
|
+
cursor = `${cursor}/${part}`;
|
|
4208
|
+
if (occupiedByNonDirectory(join6(projectDir, cursor))) return cursor;
|
|
4209
|
+
}
|
|
4210
|
+
const leaf = lstatSync(join6(projectDir, rel), { throwIfNoEntry: false });
|
|
4211
|
+
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
4212
|
+
}
|
|
4213
|
+
return null;
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4174
4216
|
// src/recorded.ts
|
|
4175
4217
|
var CLAUDE_DIR = ".claude/";
|
|
4176
4218
|
var ADVISORY_PATHS = [".env.example", ".github/workflows/", "skills-lock.json"];
|
|
@@ -4190,6 +4232,12 @@ function ownersOf(path) {
|
|
|
4190
4232
|
)
|
|
4191
4233
|
);
|
|
4192
4234
|
}
|
|
4235
|
+
function usersOf(path, projectDir) {
|
|
4236
|
+
const owners = ownersOf(path);
|
|
4237
|
+
const id = /^\.agents\/skills\/([^/]+)\//.exec(path)?.[1];
|
|
4238
|
+
const linked = id !== void 0 && linksToProjectSharedSkill(projectDir, id);
|
|
4239
|
+
return linked && !owners.includes("claude") ? [...owners, "claude"] : owners;
|
|
4240
|
+
}
|
|
4193
4241
|
function bundlePath(path) {
|
|
4194
4242
|
return path.startsWith(".agents/skills/") ? `${SKILLS_DIR}${path.slice(".agents/skills/".length)}` : path;
|
|
4195
4243
|
}
|
|
@@ -4202,9 +4250,9 @@ function recordedTrackTargets(log) {
|
|
|
4202
4250
|
selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS
|
|
4203
4251
|
};
|
|
4204
4252
|
const entries = buildManifest(spec).filter((e2) => e2.applies(spec));
|
|
4205
|
-
const templates =
|
|
4253
|
+
const templates = join7(resolveBundleRoot(), "templates");
|
|
4206
4254
|
return (path) => entries.some(
|
|
4207
|
-
(e2) => e2.type === "dir" ? path.startsWith(`${e2.target}/`) && existsSync5(
|
|
4255
|
+
(e2) => e2.type === "dir" ? path.startsWith(`${e2.target}/`) && existsSync5(join7(templates, e2.source, path.slice(e2.target.length + 1))) : e2.target === path
|
|
4208
4256
|
);
|
|
4209
4257
|
}
|
|
4210
4258
|
function recordedTrackSpots(log) {
|
|
@@ -4212,13 +4260,13 @@ function recordedTrackSpots(log) {
|
|
|
4212
4260
|
tracks: log.spec.tracks.filter(isTrack),
|
|
4213
4261
|
selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS
|
|
4214
4262
|
};
|
|
4215
|
-
const templates =
|
|
4263
|
+
const templates = join7(resolveBundleRoot(), "templates");
|
|
4216
4264
|
const out = [];
|
|
4217
4265
|
for (const e2 of buildManifest(spec)) {
|
|
4218
4266
|
if (!e2.applies(spec) || !e2.target.startsWith(CLAUDE_DIR)) continue;
|
|
4219
4267
|
if (e2.type === "file") out.push(e2.target);
|
|
4220
4268
|
else
|
|
4221
|
-
for (const rel of listFilesRecursive(
|
|
4269
|
+
for (const rel of listFilesRecursive(join7(templates, e2.source)))
|
|
4222
4270
|
out.push(`${e2.target}/${rel}`);
|
|
4223
4271
|
}
|
|
4224
4272
|
for (const p2 of RETIRED_PATHS) out.push(`${CLAUDE_DIR}${p2}`);
|
|
@@ -4297,7 +4345,6 @@ function isScaffoldPath(path) {
|
|
|
4297
4345
|
// src/install-writes.ts
|
|
4298
4346
|
var CLAUDE_DIR2 = ".claude/";
|
|
4299
4347
|
var SKILLS_DIR2 = ".claude/skills/";
|
|
4300
|
-
var BACKUP_PLACEHOLDER = "<file>.backup-<time>";
|
|
4301
4348
|
function createInstallWriter(args) {
|
|
4302
4349
|
const { projectDir, previousLog, excluded } = args;
|
|
4303
4350
|
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
@@ -4322,16 +4369,17 @@ function createInstallWriter(args) {
|
|
|
4322
4369
|
else external.set(path, sha256);
|
|
4323
4370
|
}
|
|
4324
4371
|
function harness(path, from, opts = {}) {
|
|
4325
|
-
const abs =
|
|
4372
|
+
const abs = join8(projectDir, path);
|
|
4326
4373
|
if (outside.skip(abs)) return null;
|
|
4327
4374
|
const next = "source" in from ? readFileSync4(from.source, "utf8") : from.content;
|
|
4328
4375
|
const disk = existsSync6(abs) ? readFileSync4(abs, "utf8") : null;
|
|
4329
4376
|
const rec = recorded(previousLog, path, opts);
|
|
4330
4377
|
const diskSha = rec.state === "no-sha" && disk !== null ? hashContent(disk) : null;
|
|
4378
|
+
const judgedRec = diskSha !== null && isLegacyHarnessFile(path, diskSha) ? { state: "sha", sha256: diskSha } : rec;
|
|
4331
4379
|
const j4 = judge({
|
|
4332
4380
|
op: "write",
|
|
4333
4381
|
kind: "harness",
|
|
4334
|
-
rec:
|
|
4382
|
+
rec: judgedRec,
|
|
4335
4383
|
disk,
|
|
4336
4384
|
next,
|
|
4337
4385
|
...args.run === void 0 ? {} : { run: args.run }
|
|
@@ -4376,11 +4424,19 @@ function createInstallWriter(args) {
|
|
|
4376
4424
|
default:
|
|
4377
4425
|
throw new Error(`install: '${j4.record}' is not a write record (${path})`);
|
|
4378
4426
|
}
|
|
4379
|
-
const line = backup === void 0 ? j4.line : j4.line
|
|
4427
|
+
const line = backup === void 0 ? j4.line : withBackup(j4.line, backup);
|
|
4380
4428
|
const backupPair = backup === void 0 || backupAbs === void 0 ? {} : { backup, backupAbs };
|
|
4381
|
-
|
|
4429
|
+
const state = judgedRec.state;
|
|
4430
|
+
writes.push({
|
|
4431
|
+
path,
|
|
4432
|
+
verdict: j4.verdict,
|
|
4433
|
+
record: j4.record,
|
|
4434
|
+
recorded: state,
|
|
4435
|
+
line,
|
|
4436
|
+
...backupPair
|
|
4437
|
+
});
|
|
4382
4438
|
if (j4.line === "" || j4.verdict === "create" || j4.verdict === "overwrite") return null;
|
|
4383
|
-
const out = { path, verdict: j4.verdict, line, ...backupPair };
|
|
4439
|
+
const out = { path, verdict: j4.verdict, recorded: state, line, ...backupPair };
|
|
4384
4440
|
judged.push(out);
|
|
4385
4441
|
return out;
|
|
4386
4442
|
}
|
|
@@ -4388,7 +4444,7 @@ function createInstallWriter(args) {
|
|
|
4388
4444
|
const file = SHARED_FILES[path];
|
|
4389
4445
|
if (file === void 0) throw new Error(`install: ${path} is not a shared file`);
|
|
4390
4446
|
const adapter = ADAPTERS[file.adapter];
|
|
4391
|
-
const abs =
|
|
4447
|
+
const abs = join8(projectDir, path);
|
|
4392
4448
|
const disk = existsSync6(abs) ? readFileSync4(abs, "utf8") : null;
|
|
4393
4449
|
const own = new Map(
|
|
4394
4450
|
(previousLog?.portions ?? []).filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
|
|
@@ -4570,9 +4626,9 @@ function retiredClaims(previous, projectDir, kept) {
|
|
|
4570
4626
|
const out = [];
|
|
4571
4627
|
for (const p2 of RETIRED_PATHS) {
|
|
4572
4628
|
if (have.has(p2) || recorded(previous, `${CLAUDE_DIR2}${p2}`).state === "none") continue;
|
|
4573
|
-
const abs =
|
|
4629
|
+
const abs = join8(projectDir, CLAUDE_DIR2, p2);
|
|
4574
4630
|
try {
|
|
4575
|
-
if (!
|
|
4631
|
+
if (!statSync2(abs).isFile()) continue;
|
|
4576
4632
|
out.push({ path: p2, sha256: hashContent(readFileSync4(abs, "utf8")) });
|
|
4577
4633
|
} catch {
|
|
4578
4634
|
}
|
|
@@ -4677,7 +4733,7 @@ function legacySettingsSeed(text, render, projectDir, opts = {}) {
|
|
|
4677
4733
|
} catch {
|
|
4678
4734
|
return seed;
|
|
4679
4735
|
}
|
|
4680
|
-
const claudeDir =
|
|
4736
|
+
const claudeDir = join8(projectDir, ".claude");
|
|
4681
4737
|
const candidates = new Set([...render.keys()].filter((k2) => k2.startsWith("hooks.")));
|
|
4682
4738
|
const legacyLines = /* @__PURE__ */ new Set();
|
|
4683
4739
|
const hooks = isObject2(root) && isObject2(root.hooks) ? root.hooks : {};
|
|
@@ -4794,12 +4850,12 @@ function legacyGitignoreSeed(text, render, previous) {
|
|
|
4794
4850
|
// src/installer.ts
|
|
4795
4851
|
init_esm_shims();
|
|
4796
4852
|
import { chmodSync as chmodSync3, existsSync as existsSync22, readdirSync as readdirSync5, readFileSync as readFileSync23, writeFileSync as writeFileSync8 } from "fs";
|
|
4797
|
-
import { basename as basename4, join as join31, relative as
|
|
4853
|
+
import { basename as basename4, join as join31, relative as relative6, resolve, sep as sep7 } from "path";
|
|
4798
4854
|
|
|
4799
4855
|
// src/anchor-seed.ts
|
|
4800
4856
|
init_esm_shims();
|
|
4801
4857
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
4802
|
-
import { join as
|
|
4858
|
+
import { join as join9 } from "path";
|
|
4803
4859
|
|
|
4804
4860
|
// src/agents-md-render.ts
|
|
4805
4861
|
init_esm_shims();
|
|
@@ -4906,7 +4962,7 @@ function compose(body, into) {
|
|
|
4906
4962
|
${trimmed.join("\n")}`;
|
|
4907
4963
|
}
|
|
4908
4964
|
function seedAgentsMdProjectContext(projectDir) {
|
|
4909
|
-
const root = readIfExists(
|
|
4965
|
+
const root = readIfExists(join9(projectDir, "CLAUDE.md"));
|
|
4910
4966
|
if (root === null) return null;
|
|
4911
4967
|
const withoutImport = stripHarnessImport(root) ?? root;
|
|
4912
4968
|
const body = dropHarnessQuoteBlocks(dropTitle(withoutImport.split("\n")));
|
|
@@ -4914,9 +4970,9 @@ function seedAgentsMdProjectContext(projectDir) {
|
|
|
4914
4970
|
return compose(body, "agents-md");
|
|
4915
4971
|
}
|
|
4916
4972
|
function seedRootClaudeProjectContext(projectDir, harnessRoot) {
|
|
4917
|
-
const agents = readIfExists(
|
|
4973
|
+
const agents = readIfExists(join9(projectDir, "AGENTS.md"));
|
|
4918
4974
|
if (agents === null) return null;
|
|
4919
|
-
const template = readIfExists(
|
|
4975
|
+
const template = readIfExists(join9(harnessRoot, AGENTS_MD_TEMPLATE));
|
|
4920
4976
|
if (template === null) return null;
|
|
4921
4977
|
const installer = projectContextInstallerLines({ existing: agents, template });
|
|
4922
4978
|
if (installer === null) return null;
|
|
@@ -4928,7 +4984,7 @@ function seedRootClaudeProjectContext(projectDir, harnessRoot) {
|
|
|
4928
4984
|
// src/ci-scaffold.ts
|
|
4929
4985
|
init_esm_shims();
|
|
4930
4986
|
import { existsSync as existsSync8 } from "fs";
|
|
4931
|
-
import { join as
|
|
4987
|
+
import { join as join10 } from "path";
|
|
4932
4988
|
var PYTHON_TRACKS = "data|csr-fastapi|full";
|
|
4933
4989
|
var VARIANTS = [
|
|
4934
4990
|
{
|
|
@@ -4952,12 +5008,12 @@ function installCiScaffold(ctx) {
|
|
|
4952
5008
|
for (const v2 of VARIANTS) {
|
|
4953
5009
|
if (!v2.applies(ctx.tracks)) continue;
|
|
4954
5010
|
const relTarget = `.github/workflows/${v2.target}`;
|
|
4955
|
-
const target =
|
|
5011
|
+
const target = join10(ctx.projectDir, relTarget);
|
|
4956
5012
|
if (existsSync8(target)) {
|
|
4957
5013
|
report.skippedExisting.push(relTarget);
|
|
4958
5014
|
continue;
|
|
4959
5015
|
}
|
|
4960
|
-
copyFile(
|
|
5016
|
+
copyFile(join10(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
|
|
4961
5017
|
report.written.push(relTarget);
|
|
4962
5018
|
}
|
|
4963
5019
|
return report;
|
|
@@ -4975,7 +5031,7 @@ import { basename, join as join14 } from "path";
|
|
|
4975
5031
|
|
|
4976
5032
|
// src/agents-skill-targets.ts
|
|
4977
5033
|
init_esm_shims();
|
|
4978
|
-
import { join as
|
|
5034
|
+
import { join as join11, sep as sep2 } from "path";
|
|
4979
5035
|
function bundledSkillTargets(projectDir, log = readInstallLog(projectDir)) {
|
|
4980
5036
|
if (log === null) return [];
|
|
4981
5037
|
const tracks = log.spec.tracks.filter(
|
|
@@ -4989,7 +5045,7 @@ function bundledSkillTargets(projectDir, log = readInstallLog(projectDir)) {
|
|
|
4989
5045
|
}
|
|
4990
5046
|
function allowSkillCreation(writer, projectDir, ids) {
|
|
4991
5047
|
if (ids.length === 0) return writer;
|
|
4992
|
-
const roots = ids.map((id) => `${
|
|
5048
|
+
const roots = ids.map((id) => `${join11(projectDir, ".agents", "skills", id)}${sep2}`);
|
|
4993
5049
|
return {
|
|
4994
5050
|
write(absPath, content, opts) {
|
|
4995
5051
|
const inTarget = roots.some((root) => absPath.startsWith(root));
|
|
@@ -5001,7 +5057,7 @@ function allowSkillCreation(writer, projectDir, ids) {
|
|
|
5001
5057
|
}
|
|
5002
5058
|
function agentsSkillSlot(params) {
|
|
5003
5059
|
const { projectDir, selectedInternalSkills, refreshOnly, writer } = params;
|
|
5004
|
-
const creatable = refreshOnly ? bundledSkillTargets(projectDir) : [];
|
|
5060
|
+
const creatable = refreshOnly ? bundledSkillTargets(projectDir, params.log) : [];
|
|
5005
5061
|
return {
|
|
5006
5062
|
skillIds: creatable.length === 0 ? selectedInternalSkills : [.../* @__PURE__ */ new Set([...selectedInternalSkills, ...creatable])],
|
|
5007
5063
|
skillWriter: allowSkillCreation(writer, projectDir, creatable)
|
|
@@ -5012,48 +5068,11 @@ function agentsSkillSlot(params) {
|
|
|
5012
5068
|
init_esm_shims();
|
|
5013
5069
|
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
5014
5070
|
import { dirname as dirname2, relative as relative2, sep as sep3 } from "path";
|
|
5015
|
-
|
|
5016
|
-
// src/foreign-slot.ts
|
|
5017
|
-
init_esm_shims();
|
|
5018
|
-
import { lstatSync, realpathSync, statSync as statSync2 } from "fs";
|
|
5019
|
-
import { join as join11 } from "path";
|
|
5020
|
-
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
5021
|
-
function occupiedByNonDirectory(path) {
|
|
5022
|
-
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
5023
|
-
return stat !== void 0 && !stat.isDirectory();
|
|
5024
|
-
}
|
|
5025
|
-
function linksToProjectSharedSkill(projectDir, id) {
|
|
5026
|
-
const slot = join11(projectDir, ".claude/skills", id);
|
|
5027
|
-
if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
|
|
5028
|
-
try {
|
|
5029
|
-
const real = realpathSync(slot);
|
|
5030
|
-
return real === join11(realpathSync(projectDir), ".agents/skills", id) && statSync2(real).isDirectory();
|
|
5031
|
-
} catch {
|
|
5032
|
-
return false;
|
|
5033
|
-
}
|
|
5034
|
-
}
|
|
5035
|
-
function foreignOwnedTarget(projectDir, rel) {
|
|
5036
|
-
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
5037
|
-
if (slot === void 0) return null;
|
|
5038
|
-
if (occupiedByNonDirectory(join11(projectDir, slot))) return slot;
|
|
5039
|
-
if (rel !== slot) {
|
|
5040
|
-
const parts = rel.slice(slot.length + 1).split("/");
|
|
5041
|
-
let cursor = slot;
|
|
5042
|
-
for (const part of parts.slice(0, -1)) {
|
|
5043
|
-
cursor = `${cursor}/${part}`;
|
|
5044
|
-
if (occupiedByNonDirectory(join11(projectDir, cursor))) return cursor;
|
|
5045
|
-
}
|
|
5046
|
-
const leaf = lstatSync(join11(projectDir, rel), { throwIfNoEntry: false });
|
|
5047
|
-
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
5048
|
-
}
|
|
5049
|
-
return null;
|
|
5050
|
-
}
|
|
5051
|
-
|
|
5052
|
-
// src/owned-write.ts
|
|
5053
5071
|
function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
5054
5072
|
const { now = /* @__PURE__ */ new Date(), refreshOnly = false, journal, owned = () => false } = options;
|
|
5055
5073
|
const written = /* @__PURE__ */ new Map();
|
|
5056
5074
|
const backedUp = [];
|
|
5075
|
+
const judged = [];
|
|
5057
5076
|
const backupPaths = [];
|
|
5058
5077
|
const foreignOwned = [];
|
|
5059
5078
|
const restored = [];
|
|
@@ -5078,14 +5097,30 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
5078
5097
|
const disk = existsSync9(absPath) ? readFileSync6(absPath, "utf8") : null;
|
|
5079
5098
|
if (disk === null && refreshOnly && !opts?.createInRefresh && rec.state !== "sha")
|
|
5080
5099
|
return false;
|
|
5081
|
-
const { verdict, record } = judge({
|
|
5100
|
+
const { verdict, record, line } = judge({
|
|
5101
|
+
op: "write",
|
|
5102
|
+
kind: "harness",
|
|
5103
|
+
rec,
|
|
5104
|
+
disk,
|
|
5105
|
+
next: content
|
|
5106
|
+
});
|
|
5082
5107
|
let backupRel = null;
|
|
5083
5108
|
if (verdict === "backup+overwrite" || verdict === "overwrite" && opts?.backupFirst === true) {
|
|
5084
5109
|
const backup = backupFile(absPath, now);
|
|
5085
5110
|
backupPaths.push(backup);
|
|
5086
5111
|
journal?.backup(backup);
|
|
5087
5112
|
backupRel = relative2(projectDir, backup).split(sep3).join("/");
|
|
5088
|
-
if (verdict === "backup+overwrite"
|
|
5113
|
+
if (verdict === "backup+overwrite") {
|
|
5114
|
+
if (rec.state !== "none") backedUp.push(rel);
|
|
5115
|
+
judged.push({
|
|
5116
|
+
path: rel,
|
|
5117
|
+
verdict,
|
|
5118
|
+
recorded: rec.state,
|
|
5119
|
+
line: withBackup(line, backupRel),
|
|
5120
|
+
backup: backupRel,
|
|
5121
|
+
backupAbs: backup
|
|
5122
|
+
});
|
|
5123
|
+
}
|
|
5089
5124
|
}
|
|
5090
5125
|
if (record === "displaced" || record === "displaced+sha") {
|
|
5091
5126
|
displace({ path: rel, change: "displaced", notes: backupRel === null ? [] : [backupRel] });
|
|
@@ -5111,6 +5146,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
5111
5146
|
outside: outside.list(),
|
|
5112
5147
|
files: [...written].map(([path, sha256]) => ({ path, sha256 })),
|
|
5113
5148
|
backedUp: [...backedUp],
|
|
5149
|
+
judged: [...judged],
|
|
5114
5150
|
backupPaths: [...backupPaths],
|
|
5115
5151
|
updated,
|
|
5116
5152
|
restored: [...restored],
|
|
@@ -5451,7 +5487,8 @@ function runAgentsMdTransform(params) {
|
|
|
5451
5487
|
projectDir,
|
|
5452
5488
|
selectedInternalSkills: params.selectedInternalSkills,
|
|
5453
5489
|
refreshOnly,
|
|
5454
|
-
writer
|
|
5490
|
+
writer,
|
|
5491
|
+
...params.log === void 0 ? {} : { log: params.log }
|
|
5455
5492
|
});
|
|
5456
5493
|
const creates = !exists2 && (!refreshOnly || params.installedByRecord === true);
|
|
5457
5494
|
const legacy = params.legacyContext ?? null;
|
|
@@ -6240,6 +6277,7 @@ function runCliTransforms(params) {
|
|
|
6240
6277
|
const externalFiles = [];
|
|
6241
6278
|
const externalBackups = [];
|
|
6242
6279
|
const externalBackedUp = [];
|
|
6280
|
+
const externalJudged = [];
|
|
6243
6281
|
const externalForeignOwned = [];
|
|
6244
6282
|
const displaced = /* @__PURE__ */ new Map();
|
|
6245
6283
|
let externalOutside = [];
|
|
@@ -6261,6 +6299,7 @@ function runCliTransforms(params) {
|
|
|
6261
6299
|
}
|
|
6262
6300
|
externalBackups.push(...report.ownership.backupPaths);
|
|
6263
6301
|
externalBackedUp.push(...report.ownership.backedUp);
|
|
6302
|
+
externalJudged.push(...report.ownership.judged ?? []);
|
|
6264
6303
|
for (const f of report.ownership.displaced ?? [])
|
|
6265
6304
|
if (!displaced.has(f.path)) displaced.set(f.path, f);
|
|
6266
6305
|
for (const f of report.ownership.foreignOwned) {
|
|
@@ -6368,6 +6407,7 @@ function runCliTransforms(params) {
|
|
|
6368
6407
|
externalBackups,
|
|
6369
6408
|
externalUpdated,
|
|
6370
6409
|
externalBackedUp,
|
|
6410
|
+
externalJudged,
|
|
6371
6411
|
displaced: [...displaced.values()],
|
|
6372
6412
|
externalForeignOwned,
|
|
6373
6413
|
externalOutside,
|
|
@@ -6690,14 +6730,15 @@ function refreshExternalSkills(projectDir, deps = {}) {
|
|
|
6690
6730
|
}
|
|
6691
6731
|
throw e2;
|
|
6692
6732
|
}
|
|
6693
|
-
const { report, displaced } = settle(ran);
|
|
6733
|
+
const { report, displaced, judged } = settle(ran);
|
|
6694
6734
|
recordRefreshedFiles(projectDir, log, report, displaced, deps.writeLog ?? writeInstallLog);
|
|
6695
6735
|
return {
|
|
6696
6736
|
attempted: report.attempted.length,
|
|
6697
6737
|
refreshed: report.succeeded,
|
|
6698
6738
|
failed: report.attempted.filter((r) => !r.ok).map((r) => ({ id: r.asset.id, message: r.message ?? "failed" })),
|
|
6699
6739
|
notInCatalog,
|
|
6700
|
-
unknown: false
|
|
6740
|
+
unknown: false,
|
|
6741
|
+
judged
|
|
6701
6742
|
};
|
|
6702
6743
|
}
|
|
6703
6744
|
function holdSkillSlots(projectDir, hold) {
|
|
@@ -6730,7 +6771,11 @@ function holdSkillSlots(projectDir, hold) {
|
|
|
6730
6771
|
hold.clis,
|
|
6731
6772
|
{ log: hold.log, scope: hold.scope }
|
|
6732
6773
|
);
|
|
6733
|
-
const out = {
|
|
6774
|
+
const out = {
|
|
6775
|
+
backups: [],
|
|
6776
|
+
displaced: [],
|
|
6777
|
+
judged: []
|
|
6778
|
+
};
|
|
6734
6779
|
const back = /* @__PURE__ */ new Set();
|
|
6735
6780
|
const failed = [];
|
|
6736
6781
|
for (const f of moved) {
|
|
@@ -6743,6 +6788,16 @@ function holdSkillSlots(projectDir, hold) {
|
|
|
6743
6788
|
}
|
|
6744
6789
|
out.backups.push(r.backup);
|
|
6745
6790
|
if (r.displaced !== null) out.displaced.push(r.displaced);
|
|
6791
|
+
const backup = toProjectRel(projectDir, r.backup);
|
|
6792
|
+
const line = !f.recorded ? LINES.hadFile : r.gone ? LINES.editedRemoved : LINES.edited;
|
|
6793
|
+
out.judged.push({
|
|
6794
|
+
path: f.rel,
|
|
6795
|
+
verdict: r.gone ? "backup+remove" : "backup+overwrite",
|
|
6796
|
+
recorded: f.recorded ? "sha" : "none",
|
|
6797
|
+
line: withBackup(line, backup),
|
|
6798
|
+
backup,
|
|
6799
|
+
backupAbs: r.backup
|
|
6800
|
+
});
|
|
6746
6801
|
} catch (e2) {
|
|
6747
6802
|
failed.push(`${f.rel} (${e2 instanceof Error ? e2.message : String(e2)})`);
|
|
6748
6803
|
}
|
|
@@ -6820,9 +6875,10 @@ function settleAside(projectDir, f, restore) {
|
|
|
6820
6875
|
}
|
|
6821
6876
|
const backup = backupFromCopy(f.abs, f.aside);
|
|
6822
6877
|
rmSync3(f.aside, { force: true });
|
|
6823
|
-
if (f.recorded || now === null) return { backup, displaced: null };
|
|
6878
|
+
if (f.recorded || now === null) return { backup, gone: now === null, displaced: null };
|
|
6824
6879
|
return {
|
|
6825
6880
|
backup,
|
|
6881
|
+
gone: false,
|
|
6826
6882
|
displaced: { path: f.rel, change: "displaced", notes: [toProjectRel(projectDir, backup)] }
|
|
6827
6883
|
};
|
|
6828
6884
|
}
|
|
@@ -7074,6 +7130,8 @@ function refreshLinkedSkillBodies(params) {
|
|
|
7074
7130
|
notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
|
|
7075
7131
|
files: result.files,
|
|
7076
7132
|
backupPaths: result.backupPaths,
|
|
7133
|
+
backedUp: result.backedUp,
|
|
7134
|
+
judged: result.judged ?? [],
|
|
7077
7135
|
displaced: result.displaced ?? [],
|
|
7078
7136
|
foreignOwned: result.foreignOwned,
|
|
7079
7137
|
outside: result.outside ?? []
|
|
@@ -7239,7 +7297,7 @@ import {
|
|
|
7239
7297
|
unlinkSync as unlinkSync2,
|
|
7240
7298
|
writeFileSync as writeFileSync7
|
|
7241
7299
|
} from "fs";
|
|
7242
|
-
import { basename as basename3, dirname as dirname7, join as join30 } from "path";
|
|
7300
|
+
import { basename as basename3, dirname as dirname7, join as join30, relative as relative5, sep as sep6 } from "path";
|
|
7243
7301
|
var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
|
|
7244
7302
|
function buildUpdateSpec(projectDir, tracks, only) {
|
|
7245
7303
|
const log = readInstallLog(projectDir);
|
|
@@ -7301,11 +7359,12 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7301
7359
|
const logAtStart = readInstallLog(projectDir);
|
|
7302
7360
|
const claudeManaged = logAtStart === null || installedClis(logAtStart).includes("claude");
|
|
7303
7361
|
report.claudeUnrecorded = !claudeManaged && logAtStart !== null && existsSync21(claudeDir) ? recordClaudeCommand(logAtStart, installedTracks(projectDir)) : null;
|
|
7304
|
-
const harness = logAtStart === null ? { displaced: [], restored: [], foreignOwned: [], outside: [] } : updateHarnessFiles({ projectDir, templatesDir, log: logAtStart, wants, outside, report });
|
|
7362
|
+
const harness = logAtStart === null ? { displaced: [], restored: [], foreignOwned: [], outside: [], judged: [] } : updateHarnessFiles({ projectDir, templatesDir, log: logAtStart, wants, outside, report });
|
|
7305
7363
|
const claudeDisplaced = harness.displaced;
|
|
7364
|
+
const judged = [...harness.judged];
|
|
7306
7365
|
const prunedRestored = harness.restored;
|
|
7307
7366
|
if (existsSync21(claudeDir) && claudeManaged) {
|
|
7308
|
-
if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report, outside);
|
|
7367
|
+
if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report, outside, judged);
|
|
7309
7368
|
}
|
|
7310
7369
|
const harnessMcp = renderHarnessMcp(harnessRoot, {
|
|
7311
7370
|
spec: { tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS },
|
|
@@ -7339,9 +7398,16 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7339
7398
|
if (wants("hooks")) report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
7340
7399
|
report.retiredAgents = staleAgentFiles(claudeDir);
|
|
7341
7400
|
report.demotedAgents = demotedAgentFiles(claudeDir, installedTracks(projectDir));
|
|
7342
|
-
const external = wants("external") ? refreshExternalCli(
|
|
7401
|
+
const external = wants("external") ? refreshExternalCli(
|
|
7402
|
+
projectDir,
|
|
7403
|
+
harnessRoot,
|
|
7404
|
+
legacyDroppedKeys(logAtStart),
|
|
7405
|
+
harnessMcp,
|
|
7406
|
+
report.skillsSkippedLinks.filter((id) => linksToProjectSharedSkill(projectDir, id))
|
|
7407
|
+
) : {
|
|
7343
7408
|
externalUpdated: 0,
|
|
7344
7409
|
externalBackedUp: [],
|
|
7410
|
+
externalJudged: [],
|
|
7345
7411
|
displaced: [],
|
|
7346
7412
|
externalForeignOwned: [],
|
|
7347
7413
|
externalOutside: [],
|
|
@@ -7452,10 +7518,17 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7452
7518
|
const displacedSame = displacedAll.filter((f) => f.notes.length === 0).map((f) => f.path);
|
|
7453
7519
|
if (displacedMoved.length > 0) report.displaced = displacedMoved;
|
|
7454
7520
|
if (displacedSame.length > 0) report.displacedSame = displacedSame;
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7521
|
+
const deadMcp = claudeManaged ? deadMcpLeft(projectDir) : [];
|
|
7522
|
+
if (deadMcp.length > 0) report.deadMcp = deadMcp;
|
|
7523
|
+
judged.push(...external.externalJudged, ...skillRefresh.judged ?? []);
|
|
7524
|
+
if (judged.length > 0) report.judged = judged;
|
|
7525
|
+
const unmeasured = new Set(
|
|
7526
|
+
judged.filter((j4) => j4.recorded === "no-sha" && j4.backup !== void 0).map((j4) => j4.path)
|
|
7527
|
+
);
|
|
7528
|
+
report.backups = collectRunBackups(projectDir, startedAt, backupsAtStart).map(
|
|
7529
|
+
(b2) => existsSync21(join30(projectDir, b2.path)) ? b2 : { ...b2, removed: true }
|
|
7530
|
+
);
|
|
7531
|
+
const noChecksum = report.backups.map((b2) => b2.path).filter((p2) => unmeasured.has(p2));
|
|
7459
7532
|
if (noChecksum.length > 0) report.noChecksum = noChecksum;
|
|
7460
7533
|
writeBackupList(projectDir, report.backups, new Set(noChecksum));
|
|
7461
7534
|
return report;
|
|
@@ -7533,19 +7606,16 @@ function reclaimOutOfTrackRecorded(projectDir, outside) {
|
|
|
7533
7606
|
}
|
|
7534
7607
|
return { reclaim: result, displaced };
|
|
7535
7608
|
}
|
|
7536
|
-
function
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
const anchor = log.templates.rootClaudeMd;
|
|
7547
|
-
if (anchor !== void 0) paths.add(anchor.path);
|
|
7548
|
-
return (path) => paths.has(path);
|
|
7609
|
+
function deadMcpLeft(projectDir) {
|
|
7610
|
+
let servers;
|
|
7611
|
+
try {
|
|
7612
|
+
servers = JSON.parse(readFileSync22(join30(projectDir, ".mcp.json"), "utf8")).mcpServers;
|
|
7613
|
+
} catch {
|
|
7614
|
+
return [];
|
|
7615
|
+
}
|
|
7616
|
+
return PRIOR_MCP_SERVERS.flatMap(
|
|
7617
|
+
(p2) => p2.dead !== void 0 && servers?.[p2.name] !== void 0 && jsonSha(servers[p2.name]) === jsonSha(toClaudeServer(p2.value)) ? [{ name: p2.name, note: p2.dead }] : []
|
|
7618
|
+
);
|
|
7549
7619
|
}
|
|
7550
7620
|
function retiredFeaturesPart(projectDir, f) {
|
|
7551
7621
|
if (f.path !== ".codex/config.toml") return {};
|
|
@@ -7739,6 +7809,7 @@ function updateHarnessTargets(projectDir, templatesDir, log, wants, report, fore
|
|
|
7739
7809
|
}
|
|
7740
7810
|
function updateHarnessFiles(args) {
|
|
7741
7811
|
const { projectDir, templatesDir, log, wants, outside, report } = args;
|
|
7812
|
+
const judged = [];
|
|
7742
7813
|
const now = /* @__PURE__ */ new Date();
|
|
7743
7814
|
const claude = installedClis(log).includes("claude");
|
|
7744
7815
|
const claims = retiredClaims(log, projectDir, log.policyFiles ?? []);
|
|
@@ -7759,7 +7830,7 @@ function updateHarnessFiles(args) {
|
|
|
7759
7830
|
if (!claude || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
|
|
7760
7831
|
const label = `.claude/${dir}`;
|
|
7761
7832
|
report.updated[label] = 0;
|
|
7762
|
-
const ctx = { prefix: dir, outside, recordOf };
|
|
7833
|
+
const ctx = { prefix: dir, outside, recordOf, projectDir, judged };
|
|
7763
7834
|
report.pruned[label] = pruneOrphans(
|
|
7764
7835
|
join30(projectDir, label),
|
|
7765
7836
|
join30(templatesDir, dir),
|
|
@@ -7776,7 +7847,8 @@ function updateHarnessFiles(args) {
|
|
|
7776
7847
|
targets.refreshedSkills,
|
|
7777
7848
|
outside,
|
|
7778
7849
|
recordOf,
|
|
7779
|
-
now
|
|
7850
|
+
now,
|
|
7851
|
+
judged
|
|
7780
7852
|
);
|
|
7781
7853
|
report.skillsPruned = skillPrune.pruned;
|
|
7782
7854
|
report.skillsBackedUp.push(...skillPrune.backedUp);
|
|
@@ -7785,6 +7857,11 @@ function updateHarnessFiles(args) {
|
|
|
7785
7857
|
write: true
|
|
7786
7858
|
});
|
|
7787
7859
|
const ledger = writer.ledger();
|
|
7860
|
+
judged.unshift(
|
|
7861
|
+
...ledger.writes.flatMap(
|
|
7862
|
+
({ path, verdict, recorded: recorded2, line, backup, backupAbs }) => backup === void 0 || backupAbs === void 0 ? [] : [{ path, verdict, recorded: recorded2, line, backup, backupAbs }]
|
|
7863
|
+
)
|
|
7864
|
+
);
|
|
7788
7865
|
recordHarnessWrites(projectDir, ledger, {
|
|
7789
7866
|
claims,
|
|
7790
7867
|
prunedPolicy,
|
|
@@ -7815,10 +7892,25 @@ function updateHarnessFiles(args) {
|
|
|
7815
7892
|
displaced: ledger.rootFiles.filter((f) => f.change === "displaced"),
|
|
7816
7893
|
restored,
|
|
7817
7894
|
foreignOwned,
|
|
7818
|
-
outside: ledger.outside
|
|
7895
|
+
outside: ledger.outside,
|
|
7896
|
+
judged
|
|
7897
|
+
};
|
|
7898
|
+
}
|
|
7899
|
+
function removedRow(projectDir, path, why, judged, backupAbs) {
|
|
7900
|
+
const { line, recorded: recorded2 } = judged;
|
|
7901
|
+
if (backupAbs === null) return { path, verdict: "remove", recorded: recorded2, line: `${why} \u2014 ${line}` };
|
|
7902
|
+
const backup = relative5(projectDir, backupAbs).split(sep6).join("/");
|
|
7903
|
+
return {
|
|
7904
|
+
path,
|
|
7905
|
+
verdict: "backup+remove",
|
|
7906
|
+
recorded: recorded2,
|
|
7907
|
+
line: `${why} \u2014 ${withBackup(line, backup)}`,
|
|
7908
|
+
backup,
|
|
7909
|
+
backupAbs
|
|
7819
7910
|
};
|
|
7820
7911
|
}
|
|
7821
|
-
|
|
7912
|
+
var NOT_RECORDED_REMOVED = "not on the install record \u2014 saved as <file>.backup-<time>, removed from live";
|
|
7913
|
+
function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now, judged) {
|
|
7822
7914
|
const pruned = [];
|
|
7823
7915
|
const backedUp = [];
|
|
7824
7916
|
for (const id of ids) {
|
|
@@ -7829,11 +7921,18 @@ function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now)
|
|
|
7829
7921
|
if (foreignOwnedTarget(projectDir, `.claude/skills/${id}/${rel}`) !== null) continue;
|
|
7830
7922
|
const targetFile = join30(targetSkill, rel);
|
|
7831
7923
|
if (outside.skip(targetFile)) continue;
|
|
7832
|
-
const
|
|
7833
|
-
|
|
7834
|
-
|
|
7924
|
+
const path = `.claude/skills/${id}/${rel}`;
|
|
7925
|
+
const rec = recordOf(path);
|
|
7926
|
+
const disk = rec.state === "sha" ? readFileSync22(targetFile, "utf8") : "";
|
|
7927
|
+
const line = rec.state === "none" ? NOT_RECORDED_REMOVED : judge({ op: "remove", kind: "harness", rec, disk, next: null }).line;
|
|
7928
|
+
let backupAbs = null;
|
|
7929
|
+
if (rec.state !== "sha" || hashContent(disk) !== rec.sha256) {
|
|
7930
|
+
backupAbs = backupFile(targetFile, now);
|
|
7835
7931
|
backedUp.push(`${id}/${rel}`);
|
|
7836
7932
|
}
|
|
7933
|
+
judged.push(
|
|
7934
|
+
removedRow(projectDir, path, "not in bundle", { line, recorded: rec.state }, backupAbs)
|
|
7935
|
+
);
|
|
7837
7936
|
unlinkSync2(targetFile);
|
|
7838
7937
|
pruned.push(`${id}/${rel}`);
|
|
7839
7938
|
for (let d2 = dirname7(targetFile); d2 !== targetSkill && readdirSync4(d2).length === 0; d2 = dirname7(d2))
|
|
@@ -7908,7 +8007,7 @@ function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new S
|
|
|
7908
8007
|
`${JSON.stringify(
|
|
7909
8008
|
{
|
|
7910
8009
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7911
|
-
note: "update \uAC00 \uB0A8\uAE34 \uBC31\uC5C5 \uC30D. \uC6D0\uBCF8(path)\uC740 \uCD5C\uC2E0 \uBC88\uB4E4\uD310, \uBC31\uC5C5(backup)\uC740 \uBC14\uAFB8\uAE30 \uC804 \uD310. noChecksum \uC774 \uC5C6\uB294 \uC30D\uC740 \uAE30\uB85D\uACFC \uB2EC\uB77C\uC9C4 \uD30C\uC77C = \uB2F9\uC2E0\uC758 \uD3B8\uC9D1\uBD84\uC774\uB2E4 \u2014 \uC0C8 \uD310\uC5D0 \uB2E4\uC2DC \uC5B9\uC73C\uB824\uBA74 \uB450 \uD30C\uC77C\uC758 diff \uB97C \uBCF4\uB77C. noChecksum: true \uC778 \uC30D\uC740 \uC61B \uD310\uC774 \uCCB4\uD06C\uC12C\uC744 \uAE30\uB85D\uD558\uC9C0 \uC54A\uC544 \uBE44\uAD50 \uC5C6\uC774 \uD55C \uBC88 \uBC31\uC5C5\uD55C \uAC83 \u2014 \uB300\uAC1C \uB9B4\uB9AC\uC988 \uC0AC\uC774 \uD15C\uD50C\uB9BF \uBCC0\uACBD\uBD84\uC774\uB77C \uB2F9\uC2E0\uC758 \uD3B8\uC9D1\uC774 \uC544\uB2D0 \uC218 \uC788\uB2E4.",
|
|
8010
|
+
note: "update \uAC00 \uB0A8\uAE34 \uBC31\uC5C5 \uC30D. \uC6D0\uBCF8(path)\uC740 \uCD5C\uC2E0 \uBC88\uB4E4\uD310, \uBC31\uC5C5(backup)\uC740 \uBC14\uAFB8\uAE30 \uC804 \uD310. noChecksum \uC774 \uC5C6\uB294 \uC30D\uC740 \uAE30\uB85D\uACFC \uB2EC\uB77C\uC9C4 \uD30C\uC77C = \uB2F9\uC2E0\uC758 \uD3B8\uC9D1\uBD84\uC774\uB2E4 \u2014 \uC0C8 \uD310\uC5D0 \uB2E4\uC2DC \uC5B9\uC73C\uB824\uBA74 \uB450 \uD30C\uC77C\uC758 diff \uB97C \uBCF4\uB77C. noChecksum: true \uC778 \uC30D\uC740 \uC61B \uD310\uC774 \uCCB4\uD06C\uC12C\uC744 \uAE30\uB85D\uD558\uC9C0 \uC54A\uC544 \uBE44\uAD50 \uC5C6\uC774 \uD55C \uBC88 \uBC31\uC5C5\uD55C \uAC83 \u2014 \uB300\uAC1C \uB9B4\uB9AC\uC988 \uC0AC\uC774 \uD15C\uD50C\uB9BF \uBCC0\uACBD\uBD84\uC774\uB77C \uB2F9\uC2E0\uC758 \uD3B8\uC9D1\uC774 \uC544\uB2D0 \uC218 \uC788\uB2E4. removed: true \uC778 \uC30D\uC740 \uADF8 \uD30C\uC77C\uC744 \uC9C0\uC6B0\uAE30 \uC804\uC5D0 \uB0A8\uAE34 \uAC83(\uC740\uD1F4\uD55C \uD558\uB124\uC2A4 \uD30C\uC77C \xB7 \uBC88\uB4E4\uC5D0 \uC5C6\uB294 \uC2A4\uD0AC \uD30C\uC77C \uB4F1) \u2014 \uC6D0\uBCF8 \uC790\uB9AC\uC5D0 \uC0C8 \uD310\uC774 \uC5C6\uB2E4. \uB2F9\uC2E0 \uD30C\uC77C\uC774\uC5C8\uB2E4\uBA74 \uBC31\uC5C5\uC744 \uC6D0\uB798 \uC774\uB984\uC73C\uB85C \uB418\uB3CC\uB9AC\uBA74 \uB41C\uB2E4.",
|
|
7912
8011
|
backups: backups.map((b2) => noChecksum.has(b2.path) ? { ...b2, noChecksum: true } : b2)
|
|
7913
8012
|
},
|
|
7914
8013
|
null,
|
|
@@ -7927,7 +8026,7 @@ function trackOnlyFileAssets(tracks) {
|
|
|
7927
8026
|
(e2) => e2.type === "file" && e2.applies(optedOut) && e2.applies(optedIn)
|
|
7928
8027
|
);
|
|
7929
8028
|
}
|
|
7930
|
-
function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @__PURE__ */ new Date()) {
|
|
8029
|
+
function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, now = /* @__PURE__ */ new Date()) {
|
|
7931
8030
|
if (existsSync21(join30(projectDir, LEGACY_ANCHOR_FILE2))) {
|
|
7932
8031
|
report.legacyAnchor = LEGACY_ANCHOR_FILE2;
|
|
7933
8032
|
}
|
|
@@ -7942,8 +8041,20 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
|
|
|
7942
8041
|
const recorded2 = readInstallLog(projectDir)?.templates.rootClaudeMd;
|
|
7943
8042
|
const owned = recorded2?.path === HARNESS_ANCHOR_FILE && hashContent(current) === recorded2.sha256;
|
|
7944
8043
|
if (!owned) {
|
|
7945
|
-
backupFile(anchor, now);
|
|
8044
|
+
const backup = backupFile(anchor, now);
|
|
7946
8045
|
report.anchorBackedUp = true;
|
|
8046
|
+
const rel = relative5(projectDir, backup).split(sep6).join("/");
|
|
8047
|
+
const rec = recorded2?.path === HARNESS_ANCHOR_FILE ? { state: "sha", sha256: recorded2.sha256 } : { state: "no-sha" };
|
|
8048
|
+
const next = readFileSync22(templateMd, "utf8");
|
|
8049
|
+
const { line } = judge({ op: "write", kind: "harness", rec, disk: current, next });
|
|
8050
|
+
judged.push({
|
|
8051
|
+
path: HARNESS_ANCHOR_FILE,
|
|
8052
|
+
verdict: "backup+overwrite",
|
|
8053
|
+
recorded: rec.state,
|
|
8054
|
+
line: withBackup(line, rel),
|
|
8055
|
+
backup: rel,
|
|
8056
|
+
backupAbs: backup
|
|
8057
|
+
});
|
|
7947
8058
|
}
|
|
7948
8059
|
copyFileSync(templateMd, anchor);
|
|
7949
8060
|
report.claudeMdUpdated = true;
|
|
@@ -8010,7 +8121,7 @@ function excludedSkillOf(log) {
|
|
|
8010
8121
|
const excluded = excludedIds(log);
|
|
8011
8122
|
return (id) => excluded.has(id) || isBaselineExcluded(`.claude/skills/${id}`, excluded);
|
|
8012
8123
|
}
|
|
8013
|
-
function recordedRenderInputs(projectDir, log) {
|
|
8124
|
+
function recordedRenderInputs(projectDir, log, tracks = installedTracks(projectDir)) {
|
|
8014
8125
|
const excluded = excludedIds(log);
|
|
8015
8126
|
return {
|
|
8016
8127
|
// 스킬은 **기록 + 이번 릴리즈 기본**만 넘긴다(#761 — `recordedBundledSkills`). 스킬 *파일*은 전체 목록을 넘겨도
|
|
@@ -8023,7 +8134,7 @@ function recordedRenderInputs(projectDir, log) {
|
|
|
8023
8134
|
),
|
|
8024
8135
|
// #601 — install 과 같은 SSOT(resolveRules)를 설치된 트랙으로. 렌더 경로(AGENTS.md 절 · antigravity 룰 생성)는
|
|
8025
8136
|
// 디스크 게이트가 없어, data 트랙 설치에서 update 만으로 cli-development(제6룰)가 생겼다.
|
|
8026
|
-
rules: resolveRules({ tracks
|
|
8137
|
+
rules: resolveRules({ tracks }).filter(
|
|
8027
8138
|
(r) => !isBaselineExcluded(`.claude/rules/${r}.md`, excluded)
|
|
8028
8139
|
)
|
|
8029
8140
|
};
|
|
@@ -8107,7 +8218,7 @@ function changedRemoteMcp(writes) {
|
|
|
8107
8218
|
const changed = /* @__PURE__ */ new Set([...mcp.added, ...mcp.replaced ?? []]);
|
|
8108
8219
|
return (mcp.remote ?? []).filter((r) => changed.has(r.name));
|
|
8109
8220
|
}
|
|
8110
|
-
function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
8221
|
+
function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp, linkedIds) {
|
|
8111
8222
|
const log = readInstallLog(projectDir);
|
|
8112
8223
|
const presentBefore = sharedSkillIdsOnDisk(projectDir);
|
|
8113
8224
|
const rulesBefore = /* @__PURE__ */ new Set();
|
|
@@ -8139,11 +8250,23 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
8139
8250
|
// 앞 단계(하네스 파일 쓰기)가 헬퍼만 든 `externalFiles` 를 적어도 같은 답이다 — 헬퍼는 외부 자리로 세지 않는다(B-2)
|
|
8140
8251
|
owned: recordedOwner(log)
|
|
8141
8252
|
});
|
|
8253
|
+
const linked = refreshLinkedSkillBodies({
|
|
8254
|
+
harnessRoot,
|
|
8255
|
+
projectDir,
|
|
8256
|
+
ids: linkedIds.filter(
|
|
8257
|
+
(id) => !result.externalFiles.some((f) => f.path === `${SHARED_SKILLS_DIR}/${id}/SKILL.md`)
|
|
8258
|
+
),
|
|
8259
|
+
baseline: new Map(
|
|
8260
|
+
mergeExternalFiles(log?.externalFiles, result.externalFiles).map((f) => [f.path, f.sha256])
|
|
8261
|
+
)
|
|
8262
|
+
});
|
|
8263
|
+
const written = [...result.externalFiles, ...linked.files];
|
|
8264
|
+
const displaced = [...result.displaced ?? [], ...linked.displaced];
|
|
8142
8265
|
const anchorPath = result.anchorRetired?.action === "removed" ? result.anchorRetired.path : null;
|
|
8143
8266
|
const anchorDisplaced = log === null || anchorPath === null ? [] : restoreDisplaced(log, projectDir, { has: (p2) => p2 === anchorPath, write: true });
|
|
8144
8267
|
if (log) {
|
|
8145
8268
|
const retiredPath = result.anchorRetired?.forget === true ? result.anchorRetired.path : null;
|
|
8146
|
-
const merged = mergeExternalFiles(log.externalFiles,
|
|
8269
|
+
const merged = mergeExternalFiles(log.externalFiles, written).filter(
|
|
8147
8270
|
(f) => f.path !== retiredPath
|
|
8148
8271
|
);
|
|
8149
8272
|
const next = retiredPath === null ? { ...log } : { ...log, spec: { ...log.spec, clis: [...installedClis(log)] } };
|
|
@@ -8157,7 +8280,7 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
8157
8280
|
if (portions.length > 0) next.portions = portions;
|
|
8158
8281
|
else delete next.portions;
|
|
8159
8282
|
const created = result.sharedFiles.filter((f) => f.createdAsRoot === true).map((f) => ({ path: f.path, change: "created", notes: ["\uD558\uB124\uC2A4 \uBAAB\uB9CC(\uBE14\uB85D)"] }));
|
|
8160
|
-
const roots = [...created, ...withoutLostBackups(log.rootFiles,
|
|
8283
|
+
const roots = [...created, ...withoutLostBackups(log.rootFiles, displaced)];
|
|
8161
8284
|
if (roots.length > 0) next.rootFiles = mergeRootFiles(log.rootFiles, roots);
|
|
8162
8285
|
if (anchorDisplaced.some((d2) => d2.forget))
|
|
8163
8286
|
next.rootFiles = withoutRestored(next.rootFiles, anchorDisplaced);
|
|
@@ -8216,11 +8339,12 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
8216
8339
|
const dropped = new Set(droppedKeys);
|
|
8217
8340
|
return {
|
|
8218
8341
|
externalUpdated: result.externalUpdated,
|
|
8219
|
-
externalBackedUp: result.externalBackedUp,
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8342
|
+
externalBackedUp: [...result.externalBackedUp, ...linked.backedUp],
|
|
8343
|
+
externalJudged: [...result.externalJudged, ...linked.judged],
|
|
8344
|
+
displaced,
|
|
8345
|
+
externalForeignOwned: [...result.externalForeignOwned, ...linked.foreignOwned],
|
|
8346
|
+
externalOutside: [...result.externalOutside, ...linked.outside],
|
|
8347
|
+
written: written.map((f) => f.path),
|
|
8224
8348
|
skillsInstalled,
|
|
8225
8349
|
skillsRestored,
|
|
8226
8350
|
rulesRestored,
|
|
@@ -8275,14 +8399,24 @@ function pruneOrphans(target, source, ext, ctx, now) {
|
|
|
8275
8399
|
for (const file of readdirSync4(target)) {
|
|
8276
8400
|
if (!file.endsWith(ext) || existsSync21(join30(source, file))) continue;
|
|
8277
8401
|
const targetFile = join30(target, file);
|
|
8278
|
-
const
|
|
8402
|
+
const path = `.claude/${ctx.prefix}/${file}`;
|
|
8403
|
+
const rec = ctx.recordOf(path);
|
|
8279
8404
|
try {
|
|
8280
8405
|
const disk = readFileSync22(targetFile, "utf8");
|
|
8281
|
-
const { verdict } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
|
|
8406
|
+
const { verdict, line } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
|
|
8282
8407
|
if (verdict === "leave" || ctx.outside.skip(targetFile)) continue;
|
|
8283
|
-
|
|
8408
|
+
const backupAbs = verdict === "backup+remove" ? backupFile(targetFile, now) : null;
|
|
8284
8409
|
unlinkSync2(targetFile);
|
|
8285
8410
|
removed.push(file);
|
|
8411
|
+
ctx.judged.push(
|
|
8412
|
+
removedRow(
|
|
8413
|
+
ctx.projectDir,
|
|
8414
|
+
path,
|
|
8415
|
+
"retired from the harness",
|
|
8416
|
+
{ line, recorded: rec.state },
|
|
8417
|
+
backupAbs
|
|
8418
|
+
)
|
|
8419
|
+
);
|
|
8286
8420
|
} catch {
|
|
8287
8421
|
}
|
|
8288
8422
|
}
|
|
@@ -8346,7 +8480,7 @@ function runInstall(ctx) {
|
|
|
8346
8480
|
JSON.parse(readFileSync23(settingsPath, "utf8"));
|
|
8347
8481
|
} catch {
|
|
8348
8482
|
throw new Error(
|
|
8349
|
-
`${
|
|
8483
|
+
`${relative6(process.cwd(), settingsPath)} is not valid JSON \u2014 left untouched, so no hooks were wired. Fix or delete that file, then run the install again`
|
|
8350
8484
|
);
|
|
8351
8485
|
}
|
|
8352
8486
|
}
|
|
@@ -8415,6 +8549,7 @@ function runInstallStages(ctx, args) {
|
|
|
8415
8549
|
externalBackups,
|
|
8416
8550
|
externalUpdated: _externalUpdated,
|
|
8417
8551
|
externalBackedUp: _externalBackedUp,
|
|
8552
|
+
externalJudged: _externalJudged,
|
|
8418
8553
|
displaced: cliDisplaced = [],
|
|
8419
8554
|
externalForeignOwned,
|
|
8420
8555
|
externalOutside,
|
|
@@ -8967,7 +9102,7 @@ function recordInterruptedInstall(ctx, error, run) {
|
|
|
8967
9102
|
const codexTrust = registeredTrust(journal.codexOptIn, ctx.projectDir);
|
|
8968
9103
|
const written = writtenSoFar(ledger, cliFiles, rootFiles, stage.rootImportWritten);
|
|
8969
9104
|
if (codexTrust) written.push(`${codexTrust.configPath} (Codex trust entry for this folder)`);
|
|
8970
|
-
const projectRel = (abs) =>
|
|
9105
|
+
const projectRel = (abs) => relative6(ctx.projectDir, abs).split(sep7).join("/");
|
|
8971
9106
|
const writtenSet = new Set(written);
|
|
8972
9107
|
const backups = [...own.backups, ...journal.backups].map((abs) => {
|
|
8973
9108
|
const original = projectRel(backupOriginal(abs) ?? abs);
|
|
@@ -9144,7 +9279,7 @@ function listHookFiles(hookDir) {
|
|
|
9144
9279
|
init_esm_shims();
|
|
9145
9280
|
import { homedir as homedir3 } from "os";
|
|
9146
9281
|
import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync25 } from "fs";
|
|
9147
|
-
import { join as join33, relative as
|
|
9282
|
+
import { join as join33, relative as relative7 } from "path";
|
|
9148
9283
|
|
|
9149
9284
|
// src/preset-recommend.ts
|
|
9150
9285
|
init_esm_shims();
|
|
@@ -9372,9 +9507,11 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
9372
9507
|
});
|
|
9373
9508
|
const backups = report.updateMode?.backups ?? [];
|
|
9374
9509
|
if (backups.length > 0) {
|
|
9375
|
-
const
|
|
9376
|
-
const
|
|
9377
|
-
const
|
|
9510
|
+
const replaced = backups.filter((b2) => b2.removed !== true);
|
|
9511
|
+
const removedBefore = backups.length - replaced.length;
|
|
9512
|
+
const noChecksum = new Set(report.updateMode?.noChecksum ?? []);
|
|
9513
|
+
const unmeasured = replaced.filter((b2) => noChecksum.has(b2.path)).length;
|
|
9514
|
+
const unmeasuredPart = unmeasured === 0 ? "" : unmeasured === backups.length ? " \u2014 no checksum on record for any of them (installed before checksums were kept), so they may not be your edits" : ` \u2014 ${unmeasured} ${removedBefore > 0 ? "replaced " : ""}had no checksum on record (marked noChecksum in the list), so those may not be your edits`;
|
|
9378
9515
|
const how = removedBefore === 0 ? "saved before replacing" : removedBefore === backups.length ? "saved before removing" : `saved before replacing (${removedBefore} before removing)`;
|
|
9379
9516
|
log(
|
|
9380
9517
|
infoRow(
|
|
@@ -9382,12 +9519,13 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
9382
9519
|
`${backups.length} file(s) ${how}, as *.backup-<time>${unmeasuredPart} \xB7 list: .uzys-agent-harness/update-backups.json`
|
|
9383
9520
|
)
|
|
9384
9521
|
);
|
|
9385
|
-
const
|
|
9386
|
-
|
|
9522
|
+
const displaced = new Set(report.updateMode?.displaced ?? []);
|
|
9523
|
+
const carry = replaced.filter((b2) => !displaced.has(b2.path)).length;
|
|
9524
|
+
if (carry > 0)
|
|
9387
9525
|
log(
|
|
9388
9526
|
infoRow(
|
|
9389
9527
|
"NEXT",
|
|
9390
|
-
unmeasured ===
|
|
9528
|
+
unmeasured === carry ? 'nothing to carry over unless you remember editing one of them \u2014 then ask audit-harness-fit: "update \uBC31\uC5C5\uBCF8\uC758 \uB0B4 \uD3B8\uC9D1\uC744 \uB0B4 \uD30C\uC77C\uB85C \uC62E\uACA8\uC918"' : 'backups are not loaded \u2014 to keep your edits across updates, ask audit-harness-fit: "update \uBC31\uC5C5\uBCF8\uC758 \uB0B4 \uD3B8\uC9D1\uC744 \uB0B4 \uD30C\uC77C\uB85C \uC62E\uACA8\uC918" (update replaces harness files every time, so edits put back inside them return as backups)'
|
|
9391
9529
|
)
|
|
9392
9530
|
);
|
|
9393
9531
|
}
|
|
@@ -9779,25 +9917,8 @@ function linkedSkillRows(log, updated, notOurs) {
|
|
|
9779
9917
|
);
|
|
9780
9918
|
}
|
|
9781
9919
|
}
|
|
9782
|
-
function hadFileRow(log, area, paths) {
|
|
9783
|
-
if (paths.length === 0) return;
|
|
9784
|
-
log(
|
|
9785
|
-
assetRow(
|
|
9786
|
-
"skip",
|
|
9787
|
-
`${area} had files with these names`,
|
|
9788
|
-
`${paths.length} backed up as *.backup-<time> \u2014 yours, put back on uninstall \xB7 ${paths.join(", ")}`
|
|
9789
|
-
)
|
|
9790
|
-
);
|
|
9791
|
-
}
|
|
9792
9920
|
function renderPhase1Rows(log, baseline, verbose = false) {
|
|
9793
9921
|
if (baseline.updateMode) {
|
|
9794
|
-
const noChecksum = new Set(baseline.updateMode.noChecksum ?? []);
|
|
9795
|
-
const displaced = baseline.updateMode.displaced ?? [];
|
|
9796
|
-
const displacedSkills = displaced.filter((p2) => p2.startsWith(".claude/skills/"));
|
|
9797
|
-
const displacedPolicy = displaced.filter(
|
|
9798
|
-
(p2) => p2.startsWith(".claude/") && !p2.startsWith(".claude/skills/")
|
|
9799
|
-
);
|
|
9800
|
-
const displacedExternal = displaced.filter((p2) => !p2.startsWith(".claude/"));
|
|
9801
9922
|
for (const [dir, count] of Object.entries(baseline.updateMode.updated)) {
|
|
9802
9923
|
if (count > 0) log(assetRow("success", dir, `${count} files updated`));
|
|
9803
9924
|
}
|
|
@@ -9854,6 +9975,8 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9854
9975
|
const row = sharedFileRow(f);
|
|
9855
9976
|
if (row !== null) log(row);
|
|
9856
9977
|
}
|
|
9978
|
+
for (const d2 of baseline.updateMode.deadMcp ?? [])
|
|
9979
|
+
log(assetRow("skip", ".mcp.json", `${d2.name} \u2014 ${d2.note}`));
|
|
9857
9980
|
for (const f of baseline.updateMode.sharedLeft ?? []) {
|
|
9858
9981
|
log(sharedLeftRow(f));
|
|
9859
9982
|
if (f.retiredFeatures !== void 0) log(retiredFeaturesRow(f.path, f.retiredFeatures));
|
|
@@ -9867,11 +9990,7 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9867
9990
|
if (legacy2 !== null) log(legacy2);
|
|
9868
9991
|
for (const row of legacyReleasedCatalogRows(baseline.updateMode.legacyReleasedCatalog ?? []))
|
|
9869
9992
|
log(row);
|
|
9870
|
-
for (const
|
|
9871
|
-
if (removed.length > 0) {
|
|
9872
|
-
log(assetRow("skip", `${dir} orphan prune`, `${removed.length} removed`));
|
|
9873
|
-
}
|
|
9874
|
-
}
|
|
9993
|
+
for (const j4 of baseline.updateMode.judged ?? []) log(judgedRow(j4));
|
|
9875
9994
|
if (baseline.updateMode.skippedGroups.length > 0) {
|
|
9876
9995
|
log(
|
|
9877
9996
|
assetRow(
|
|
@@ -9884,15 +10003,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9884
10003
|
if (baseline.updateMode.claudeMdUpdated) {
|
|
9885
10004
|
log(assetRow("success", HARNESS_ANCHOR_FILE, "refreshed from template"));
|
|
9886
10005
|
}
|
|
9887
|
-
if (baseline.updateMode.anchorBackedUp && !noChecksum.has(HARNESS_ANCHOR_FILE)) {
|
|
9888
|
-
log(
|
|
9889
|
-
assetRow(
|
|
9890
|
-
"skip",
|
|
9891
|
-
`${HARNESS_ANCHOR_FILE} edited`,
|
|
9892
|
-
"your edits backed up as *.backup-<time> \u2014 latest template is now active"
|
|
9893
|
-
)
|
|
9894
|
-
);
|
|
9895
|
-
}
|
|
9896
10006
|
if (baseline.updateMode.anchorCreated) {
|
|
9897
10007
|
log(assetRow("success", HARNESS_ANCHOR_FILE, "created from template (anchor migration)"));
|
|
9898
10008
|
}
|
|
@@ -9920,28 +10030,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9920
10030
|
if (baseline.updateMode.legacyAnchor) {
|
|
9921
10031
|
log(assetRow("skip", baseline.updateMode.legacyAnchor, LEGACY_ANCHOR_NOTE));
|
|
9922
10032
|
}
|
|
9923
|
-
const skillsEdited = baseline.updateMode.skillsBackedUp.filter(
|
|
9924
|
-
(p2) => !noChecksum.has(`.claude/skills/${p2}`)
|
|
9925
|
-
);
|
|
9926
|
-
if (skillsEdited.length > 0) {
|
|
9927
|
-
log(
|
|
9928
|
-
assetRow(
|
|
9929
|
-
"skip",
|
|
9930
|
-
".claude/skills edited files",
|
|
9931
|
-
`${skillsEdited.length} backed up as *.backup-<time>`
|
|
9932
|
-
)
|
|
9933
|
-
);
|
|
9934
|
-
}
|
|
9935
|
-
hadFileRow(log, ".claude/skills", displacedSkills);
|
|
9936
|
-
if (baseline.updateMode.skillsPruned.length > 0) {
|
|
9937
|
-
log(
|
|
9938
|
-
assetRow(
|
|
9939
|
-
"success",
|
|
9940
|
-
".claude/skills not in bundle",
|
|
9941
|
-
`${baseline.updateMode.skillsPruned.length} files deleted \xB7 ${baseline.updateMode.skillsPruned.join(", ")}`
|
|
9942
|
-
)
|
|
9943
|
-
);
|
|
9944
|
-
}
|
|
9945
10033
|
linkedSkillRows(
|
|
9946
10034
|
log,
|
|
9947
10035
|
baseline.updateMode.skillsLinked ?? [],
|
|
@@ -9995,29 +10083,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9995
10083
|
)
|
|
9996
10084
|
);
|
|
9997
10085
|
}
|
|
9998
|
-
const policyEdited = baseline.updateMode.policyBackedUp.filter(
|
|
9999
|
-
(p2) => !noChecksum.has(`.claude/${p2}`)
|
|
10000
|
-
);
|
|
10001
|
-
if (policyEdited.length > 0) {
|
|
10002
|
-
log(
|
|
10003
|
-
assetRow(
|
|
10004
|
-
"skip",
|
|
10005
|
-
"edited policy files",
|
|
10006
|
-
`${policyEdited.length} backed up as *.backup-<time>`
|
|
10007
|
-
)
|
|
10008
|
-
);
|
|
10009
|
-
}
|
|
10010
|
-
hadFileRow(log, "policy files", displacedPolicy);
|
|
10011
|
-
const unmeasured = [...noChecksum].filter((p2) => p2 !== ".mcp-allowlist");
|
|
10012
|
-
if (unmeasured.length > 0) {
|
|
10013
|
-
log(
|
|
10014
|
-
assetRow(
|
|
10015
|
-
"skip",
|
|
10016
|
-
"no checksum on record",
|
|
10017
|
-
`${unmeasured.length} backed up once as *.backup-<time> \u2014 installed before checksums were kept, so these may not be your edits (usually what changed between releases); later updates compare precisely`
|
|
10018
|
-
)
|
|
10019
|
-
);
|
|
10020
|
-
}
|
|
10021
10086
|
const retiredRefs = baseline.updateMode.retiredHookRefsRemoved ?? [];
|
|
10022
10087
|
if (retiredRefs.length > 0) {
|
|
10023
10088
|
log(
|
|
@@ -10067,17 +10132,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
10067
10132
|
)
|
|
10068
10133
|
);
|
|
10069
10134
|
}
|
|
10070
|
-
const externalEdited = baseline.updateMode.externalBackedUp.filter((p2) => !noChecksum.has(p2));
|
|
10071
|
-
if (externalEdited.length > 0) {
|
|
10072
|
-
log(
|
|
10073
|
-
assetRow(
|
|
10074
|
-
"skip",
|
|
10075
|
-
"edited external CLI files",
|
|
10076
|
-
`${externalEdited.length} backed up as *.backup-<time>`
|
|
10077
|
-
)
|
|
10078
|
-
);
|
|
10079
|
-
}
|
|
10080
|
-
hadFileRow(log, "external CLI files", displacedExternal);
|
|
10081
10135
|
const displacedSame = baseline.updateMode.displacedSame ?? [];
|
|
10082
10136
|
if (displacedSame.length > 0) {
|
|
10083
10137
|
log(
|
|
@@ -10538,7 +10592,7 @@ function renderInterruptedInstall(err, e2, cwd = process.cwd()) {
|
|
|
10538
10592
|
}
|
|
10539
10593
|
const { path, error } = e2.record;
|
|
10540
10594
|
err(
|
|
10541
|
-
path !== null ? ` Already in place before it stopped \u2014 recorded in ${
|
|
10595
|
+
path !== null ? ` Already in place before it stopped \u2014 recorded in ${relative7(cwd, path) || path}:` : " Already in place before it stopped:"
|
|
10542
10596
|
);
|
|
10543
10597
|
for (const [key, n] of groups) {
|
|
10544
10598
|
err(` ${key.endsWith("/") ? `${key} (${n} file${n === 1 ? "" : "s"})` : key}`);
|
|
@@ -11011,7 +11065,56 @@ import {
|
|
|
11011
11065
|
statSync as statSync5,
|
|
11012
11066
|
writeFileSync as writeFileSync9
|
|
11013
11067
|
} from "fs";
|
|
11014
|
-
import { dirname as dirname8, join as join35, relative as
|
|
11068
|
+
import { dirname as dirname8, join as join35, relative as relative8, resolve as resolve4, sep as sep8 } from "path";
|
|
11069
|
+
|
|
11070
|
+
// src/track-removal.ts
|
|
11071
|
+
init_esm_shims();
|
|
11072
|
+
function trackRemoval(log, target) {
|
|
11073
|
+
const known = log.spec.tracks.filter(isTrack);
|
|
11074
|
+
const remaining = known.filter((t) => t !== target);
|
|
11075
|
+
const stays = claudeTargets(remaining);
|
|
11076
|
+
const only = [...claudeTargets(known)].filter((p2) => !stays.has(p2));
|
|
11077
|
+
const has = (path) => only.some((t) => claudeSpot(path) === t || claudeSpot(path).startsWith(`${t}/`));
|
|
11078
|
+
return { target, known, remaining, has };
|
|
11079
|
+
}
|
|
11080
|
+
function claudeSpot(path) {
|
|
11081
|
+
return bundlePath(path).replace(/^\.agents\/rules\//, ".claude/rules/");
|
|
11082
|
+
}
|
|
11083
|
+
function trackRemovalNames(removed, target) {
|
|
11084
|
+
const groups = /* @__PURE__ */ new Map();
|
|
11085
|
+
for (const p2 of removed) {
|
|
11086
|
+
const m = /^\.claude\/([^/]+)\/([^/]+?)(?:\.md)?(?:\/|$)/.exec(claudeSpot(p2));
|
|
11087
|
+
const [kind, name] = m ? [m[1] ?? "", m[2] ?? ""] : ["files", p2];
|
|
11088
|
+
groups.set(kind, (groups.get(kind) ?? /* @__PURE__ */ new Set()).add(name));
|
|
11089
|
+
}
|
|
11090
|
+
const names = [...groups].sort(([a], [b2]) => a.localeCompare(b2)).map(([kind, names2]) => `${kind} ${[...names2].sort().join(", ")}`);
|
|
11091
|
+
return `removes: ${names.join(" \xB7 ") || `nothing only ${target} installs`}`;
|
|
11092
|
+
}
|
|
11093
|
+
function trackLeftLines(log, r, harnessRoot) {
|
|
11094
|
+
const recorded2 = new Set(log.assets.map((a) => a.id));
|
|
11095
|
+
const def = (a, tracks) => shouldInstallAsset(a, { tracks, options: DEFAULT_OPTIONS });
|
|
11096
|
+
const mine = (a) => def(a, r.known) && !def(a, r.remaining);
|
|
11097
|
+
const lines2 = EXTERNAL_ASSETS.filter(
|
|
11098
|
+
(a) => a.method.kind !== "internal" && recorded2.has(a.id) && mine(a)
|
|
11099
|
+
).map(
|
|
11100
|
+
(a) => `asset ${a.id} (a default of ${r.target}) \u2014 remove with: agent-harness uninstall --only ${a.id}`
|
|
11101
|
+
);
|
|
11102
|
+
const keys = (log.portions ?? []).filter((p2) => p2.path === ".mcp.json").map((p2) => p2.key);
|
|
11103
|
+
const staysMcp = new Set(trackDefaultMcpAssetIds(harnessRoot, r.remaining));
|
|
11104
|
+
const mcp = trackDefaultMcpAssetIds(harnessRoot, r.known).filter(
|
|
11105
|
+
(id) => !staysMcp.has(id) && keys.includes(`mcpServers.${id}`)
|
|
11106
|
+
);
|
|
11107
|
+
if (mcp.length === 0) return lines2;
|
|
11108
|
+
const flags = [
|
|
11109
|
+
r.remaining.map((t) => `--track ${t}`),
|
|
11110
|
+
installedClis(log).map((c3) => `--cli ${c3}`)
|
|
11111
|
+
];
|
|
11112
|
+
const command = `agent-harness install ${[...flags.flat(), ...mcp.map((id) => `--without ${id}`)].join(" ")}`;
|
|
11113
|
+
return [
|
|
11114
|
+
...lines2,
|
|
11115
|
+
`MCP servers ${mcp.join(" \xB7 ")} (defaults of ${r.target}, kept because your install record has them) \u2014 drop: ${command} (this re-runs the whole install: harness files refreshed, edited ones backed up; --without stays on record, so add --with <id> to get one back later)`
|
|
11116
|
+
];
|
|
11117
|
+
}
|
|
11015
11118
|
|
|
11016
11119
|
// src/uninstall-interactive.ts
|
|
11017
11120
|
init_esm_shims();
|
|
@@ -12057,7 +12160,7 @@ function buildRouterChoices(_state, record) {
|
|
|
12057
12160
|
{
|
|
12058
12161
|
value: "uninstall",
|
|
12059
12162
|
label: "Uninstall",
|
|
12060
|
-
hint: "remove everything, one CLI, or single assets",
|
|
12163
|
+
hint: "remove everything, one CLI, one track, or single assets",
|
|
12061
12164
|
enabled: true
|
|
12062
12165
|
},
|
|
12063
12166
|
{
|
|
@@ -12420,7 +12523,7 @@ function hintFor(asset) {
|
|
|
12420
12523
|
return 'no automatic reverse path \u2014 only "Remove everything" clears it';
|
|
12421
12524
|
}
|
|
12422
12525
|
}
|
|
12423
|
-
function buildUninstallModeChoices(clis, assetCount) {
|
|
12526
|
+
function buildUninstallModeChoices(clis, assetCount, tracks) {
|
|
12424
12527
|
return [
|
|
12425
12528
|
{
|
|
12426
12529
|
value: "cli",
|
|
@@ -12428,6 +12531,7 @@ function buildUninstallModeChoices(clis, assetCount) {
|
|
|
12428
12531
|
hint: clis.length > 1 ? "that CLI's own files go; files shared with a remaining CLI stay; your text stays" : 'only one CLI here \u2014 use "Remove everything"',
|
|
12429
12532
|
enabled: clis.length > 1
|
|
12430
12533
|
},
|
|
12534
|
+
...tracks === void 0 ? [] : [trackMode(tracks.length > 1)],
|
|
12431
12535
|
{
|
|
12432
12536
|
value: "selected",
|
|
12433
12537
|
label: "Remove selected assets",
|
|
@@ -12442,6 +12546,12 @@ function buildUninstallModeChoices(clis, assetCount) {
|
|
|
12442
12546
|
}
|
|
12443
12547
|
];
|
|
12444
12548
|
}
|
|
12549
|
+
var trackMode = (many) => ({
|
|
12550
|
+
value: "track",
|
|
12551
|
+
label: "Remove one track",
|
|
12552
|
+
hint: many ? "only that track's own files go" : 'only one track here \u2014 use "Remove everything"',
|
|
12553
|
+
enabled: many
|
|
12554
|
+
});
|
|
12445
12555
|
var CLI_FOLDERS = [".claude/", ".codex/", ".opencode/"];
|
|
12446
12556
|
var FOLDER_CLI = {
|
|
12447
12557
|
".claude/": "claude",
|
|
@@ -12522,7 +12632,7 @@ async function runInteractiveUninstall(projectDir, deps = {}) {
|
|
|
12522
12632
|
const rows2 = buildRemovableRows(log.assets);
|
|
12523
12633
|
const clis = installedClis(log);
|
|
12524
12634
|
const mode = await prompts.selectMode(
|
|
12525
|
-
buildUninstallModeChoices(clis, rows2.length),
|
|
12635
|
+
buildUninstallModeChoices(clis, rows2.length, log.spec.tracks.filter(isTrack)),
|
|
12526
12636
|
`uzys-agent-harness \xB7 uninstall installed: ${clis.join(", ") || "(none)"} \xB7 assets ${rows2.length}`
|
|
12527
12637
|
);
|
|
12528
12638
|
if (mode === null) {
|
|
@@ -12530,6 +12640,7 @@ async function runInteractiveUninstall(projectDir, deps = {}) {
|
|
|
12530
12640
|
return { ok: false, reason: "cancelled" };
|
|
12531
12641
|
}
|
|
12532
12642
|
if (mode === "cli") return pickCli(projectDir, clis, prompts);
|
|
12643
|
+
if (mode === "track") return pickTrack(projectDir, log, prompts);
|
|
12533
12644
|
if (mode === "all") {
|
|
12534
12645
|
const ok2 = await prompts.confirm(
|
|
12535
12646
|
[
|
|
@@ -12592,6 +12703,26 @@ async function pickCli(projectDir, clis, prompts) {
|
|
|
12592
12703
|
}
|
|
12593
12704
|
return { ok: true, options: { projectDir, cli } };
|
|
12594
12705
|
}
|
|
12706
|
+
async function pickTrack(projectDir, log, prompts) {
|
|
12707
|
+
const rows2 = log.spec.tracks.filter(isTrack).map((t) => ({ value: t, label: TRACK_LABELS[t], hint: t }));
|
|
12708
|
+
const track = await prompts.selectTrack(rows2);
|
|
12709
|
+
const left = rows2.map((r) => r.value).filter((t) => t !== track);
|
|
12710
|
+
const ok = track !== null && await prompts.confirm(
|
|
12711
|
+
[
|
|
12712
|
+
`Remove ${track} (remaining: ${left.join(", ")})? Same as: agent-harness uninstall --track ${track}`,
|
|
12713
|
+
...previewTrackRemoval(
|
|
12714
|
+
{ installLog: log, projectDir, harnessRoot: resolveBundleRoot() },
|
|
12715
|
+
track
|
|
12716
|
+
).map((l) => ` \xB7 ${l}`),
|
|
12717
|
+
` \xB7 file counts: agent-harness uninstall --track ${track} --dry-run`
|
|
12718
|
+
].join("\n")
|
|
12719
|
+
);
|
|
12720
|
+
if (!ok) {
|
|
12721
|
+
prompts.cancel("Cancelled.");
|
|
12722
|
+
return { ok: false, reason: "cancelled" };
|
|
12723
|
+
}
|
|
12724
|
+
return { ok: true, options: { projectDir, track } };
|
|
12725
|
+
}
|
|
12595
12726
|
function defaultUninstallPrompts() {
|
|
12596
12727
|
return {
|
|
12597
12728
|
intro: (m) => ge(m),
|
|
@@ -12616,6 +12747,13 @@ function defaultUninstallPrompts() {
|
|
|
12616
12747
|
});
|
|
12617
12748
|
return q(r) ? null : r;
|
|
12618
12749
|
},
|
|
12750
|
+
selectTrack: async (rows2) => {
|
|
12751
|
+
const r = await Ee({
|
|
12752
|
+
message: 'Which track? (the last remaining track cannot be removed this way \u2014 use "Remove everything")',
|
|
12753
|
+
options: rows2.map((x) => ({ value: x.value, label: x.label, hint: x.hint }))
|
|
12754
|
+
});
|
|
12755
|
+
return q(r) ? null : r;
|
|
12756
|
+
},
|
|
12619
12757
|
selectAssets: async (rows2) => {
|
|
12620
12758
|
if (rows2.length === 0) return [];
|
|
12621
12759
|
const r = await ve({
|
|
@@ -12688,6 +12826,13 @@ function uninstallAction(options, deps = {}) {
|
|
|
12688
12826
|
exit(1);
|
|
12689
12827
|
return;
|
|
12690
12828
|
}
|
|
12829
|
+
if (options.track !== void 0) {
|
|
12830
|
+
removeTrackAction(
|
|
12831
|
+
{ options, installLog, projectDir, harnessRoot },
|
|
12832
|
+
{ log, err, exit, rm, writeLog }
|
|
12833
|
+
);
|
|
12834
|
+
return;
|
|
12835
|
+
}
|
|
12691
12836
|
if (options.cli !== void 0) {
|
|
12692
12837
|
removeCliAction(
|
|
12693
12838
|
{ options, installLog, projectDir, harnessRoot },
|
|
@@ -12940,12 +13085,7 @@ function settleLog(ctx, io) {
|
|
|
12940
13085
|
const recordDir = join35(projectDir, INSTALL_LOG_DIR);
|
|
12941
13086
|
if (outsideLinkAt(projectDir, INSTALL_LOG_DIR) !== null) io.rm(recordDir);
|
|
12942
13087
|
else {
|
|
12943
|
-
for (const rel of
|
|
12944
|
-
`${INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`,
|
|
12945
|
-
...HARNESS_RUNTIME_IGNORES
|
|
12946
|
-
]) {
|
|
12947
|
-
io.rm(join35(projectDir, rel));
|
|
12948
|
-
}
|
|
13088
|
+
for (const rel of RECORD_FILES) io.rm(join35(projectDir, rel));
|
|
12949
13089
|
pruneEmptyDirsUpward(projectDir, recordDir);
|
|
12950
13090
|
}
|
|
12951
13091
|
if (outsideLinkAt(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
|
|
@@ -13094,50 +13234,53 @@ var TEMPLATE_DIR_FIELD = {
|
|
|
13094
13234
|
codex: "codexDir",
|
|
13095
13235
|
opencode: "opencodeDir"
|
|
13096
13236
|
};
|
|
13097
|
-
function
|
|
13098
|
-
const
|
|
13099
|
-
|
|
13100
|
-
io.err(
|
|
13101
|
-
io.err(
|
|
13102
|
-
c.dim(
|
|
13103
|
-
" CLI \uD558\uB098 \uC81C\uAC70: uninstall --cli <name> / \uC790\uC0B0 \uD558\uB098 \uC81C\uAC70: uninstall --only <id>"
|
|
13104
|
-
)
|
|
13105
|
-
);
|
|
13237
|
+
function resolveScopeTarget(io, s) {
|
|
13238
|
+
const reject = (msg, hint) => {
|
|
13239
|
+
io.err(status.failure(c.red(`ERROR: ${msg}`)));
|
|
13240
|
+
io.err(c.dim(` ${hint}`));
|
|
13106
13241
|
io.exit(1);
|
|
13107
13242
|
return null;
|
|
13243
|
+
};
|
|
13244
|
+
if (s.conflict) {
|
|
13245
|
+
return reject(
|
|
13246
|
+
`--${s.flag} \uB294 --only \xB7 --cli \xB7 --track \uC911 \uD558\uB098\uB9CC \uC4F8 \uC218 \uC788\uB2E4`,
|
|
13247
|
+
"CLI \uD558\uB098: uninstall --cli <name> / \uD2B8\uB799 \uD558\uB098: uninstall --track <name> / \uC790\uC0B0: uninstall --only <id>"
|
|
13248
|
+
);
|
|
13108
13249
|
}
|
|
13109
|
-
const target = (
|
|
13110
|
-
if (!
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13250
|
+
const target = String(s.value ?? "").trim();
|
|
13251
|
+
if (!s.vocab.includes(target)) {
|
|
13252
|
+
return reject(
|
|
13253
|
+
`Invalid --${s.flag} value: ${target || "(empty)"}`,
|
|
13254
|
+
`Must be one of: ${s.vocab.join(" | ")}`
|
|
13255
|
+
);
|
|
13115
13256
|
}
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
return null;
|
|
13257
|
+
if (!s.installed.includes(target)) {
|
|
13258
|
+
return reject(
|
|
13259
|
+
`${target} is not installed in this project`,
|
|
13260
|
+
`installed: ${s.installed.join(", ") || "(none)"}`
|
|
13261
|
+
);
|
|
13122
13262
|
}
|
|
13123
|
-
if (installed.length <= 1) {
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13263
|
+
if (s.installed.length <= 1) {
|
|
13264
|
+
return reject(
|
|
13265
|
+
`${target} \uB294 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB9C8\uC9C0\uB9C9 ${s.flag === "cli" ? "CLI \uB2E4" : "\uD2B8\uB799\uC774\uB2E4"}`,
|
|
13266
|
+
"\uC804\uB7C9 \uC81C\uAC70\uB294 `agent-harness uninstall` \uC744 \uC4F4\uB2E4 (\uC790\uC0B0\xB7\uC124\uCE58 \uAE30\uB85D\uAE4C\uC9C0 \uD568\uAED8)"
|
|
13267
|
+
);
|
|
13128
13268
|
}
|
|
13129
13269
|
return target;
|
|
13130
13270
|
}
|
|
13131
13271
|
function underAny(path, owned) {
|
|
13132
13272
|
return owned.some((o) => o.endsWith("/") ? path.startsWith(o) : path === o);
|
|
13133
13273
|
}
|
|
13134
|
-
function planCliRemoval(target, remaining) {
|
|
13274
|
+
function planCliRemoval(target, remaining, log, projectDir) {
|
|
13135
13275
|
const { exclusive, sharedNowUnowned } = removableFor(target, remaining);
|
|
13136
13276
|
const owned = [...exclusive, ...sharedNowUnowned];
|
|
13137
13277
|
const prefixes = owned.filter((o) => o.kind === "recorded" || o.kind === "anchor").map((o) => o.path);
|
|
13138
|
-
const
|
|
13278
|
+
const used = (path) => usersOf(path, projectDir).some((cli) => remaining.includes(cli));
|
|
13279
|
+
const has = (path) => underAny(path, prefixes) && !used(path);
|
|
13280
|
+
const kept = harnessCandidates(log).filter((p2) => underAny(p2, prefixes) && used(p2)).map((p2) => p2.split("/").slice(0, 3).join("/"));
|
|
13139
13281
|
return {
|
|
13140
13282
|
target,
|
|
13283
|
+
kept: [...new Set(kept)].sort(),
|
|
13141
13284
|
scope: {
|
|
13142
13285
|
has,
|
|
13143
13286
|
// `.claude/settings.json` 은 claude 의 `.claude/` 아래 · `.codex/config.toml` 은 codex 의 `.codex/` 아래 · `AGENTS.md` ·
|
|
@@ -13151,40 +13294,16 @@ function planCliRemoval(target, remaining) {
|
|
|
13151
13294
|
}
|
|
13152
13295
|
};
|
|
13153
13296
|
}
|
|
13154
|
-
function
|
|
13155
|
-
return [
|
|
13156
|
-
|
|
13157
|
-
|
|
13158
|
-
"",
|
|
13159
|
-
c.dim(` installed: ${installed.join(", ")}`),
|
|
13160
|
-
c.dim(` removing: ${target}`),
|
|
13161
|
-
c.dim(` remaining: ${remaining.join(", ")}`),
|
|
13162
|
-
""
|
|
13163
|
-
];
|
|
13164
|
-
}
|
|
13165
|
-
function removeCliDryRunLines(plan, ctx, remaining) {
|
|
13166
|
-
const { installLog, projectDir, harnessRoot } = ctx;
|
|
13167
|
-
const lines2 = [c.yellow("[DRY RUN] CLI \uC81C\uAC70 \uBBF8\uB9AC\uBCF4\uAE30 (\uC2E4\uC81C \uBCC0\uACBD \uC5C6\uC74C):"), ""];
|
|
13168
|
-
const preview = removeHarnessFiles(installLog, projectDir, plan.scope, {
|
|
13169
|
-
rm: () => {
|
|
13170
|
-
},
|
|
13171
|
-
write: false,
|
|
13172
|
-
harnessRoot
|
|
13173
|
-
});
|
|
13174
|
-
lines2.push(...harnessRemovalLines(preview, installLog, projectDir, plan.scope, true));
|
|
13175
|
-
lines2.push(...assetsLeftLines(installLog, projectDir, plan.scope));
|
|
13176
|
-
const rerender = agentsMdRerenderSet(plan.target, remaining, installLog, projectDir);
|
|
13177
|
-
if (rerender !== null) {
|
|
13178
|
-
lines2.push(` \u25CB would re-render AGENTS.md for ${displayNames(rerender)} (your sections kept)`);
|
|
13179
|
-
}
|
|
13180
|
-
lines2.push(` \u25CB install log updated (clis: ${remaining.join(", ")})`, "");
|
|
13181
|
-
return lines2;
|
|
13297
|
+
function keptForRemainingLines(plan, preview) {
|
|
13298
|
+
if (plan.kept.length === 0) return [];
|
|
13299
|
+
const what = `${plan.kept.join(" \xB7 ")} \u2014 still used by Claude Code through its .claude/skills/ link(s)`;
|
|
13300
|
+
return [preview ? ` \u25CB leave ${what}` : ` ${c.yellow("\u2298")} left ${what}`];
|
|
13182
13301
|
}
|
|
13183
13302
|
function displayNames(clis) {
|
|
13184
13303
|
return clis.map((c3) => CLI_DISPLAY_NAMES[c3]).join(" \xB7 ");
|
|
13185
13304
|
}
|
|
13186
13305
|
function agentsMdRerenderSet(target, remaining, log, projectDir) {
|
|
13187
|
-
if (!isAgentsMdCli(target)) return null;
|
|
13306
|
+
if (target !== null && !isAgentsMdCli(target)) return null;
|
|
13188
13307
|
const set = agentsMdClis(remaining);
|
|
13189
13308
|
if (set.length === 0) return null;
|
|
13190
13309
|
const recorded2 = (log.externalFiles ?? []).some((f) => f.path === AGENTS_MD2) || (log.portions ?? []).some((p2) => p2.path === AGENTS_MD2);
|
|
@@ -13201,17 +13320,18 @@ function readTextOrEmpty(abs) {
|
|
|
13201
13320
|
return "";
|
|
13202
13321
|
}
|
|
13203
13322
|
}
|
|
13204
|
-
function rerenderAgentsMd(ctx, set) {
|
|
13205
|
-
const {
|
|
13323
|
+
function rerenderAgentsMd(ctx, set, log) {
|
|
13324
|
+
const { projectDir, harnessRoot } = ctx;
|
|
13206
13325
|
return runAgentsMdTransform({
|
|
13207
13326
|
harnessRoot,
|
|
13208
13327
|
projectDir,
|
|
13209
13328
|
clis: agentsMdClis(set),
|
|
13210
|
-
...recordedRenderInputs(projectDir,
|
|
13211
|
-
baseline: new Map((
|
|
13329
|
+
...recordedRenderInputs(projectDir, log, log.spec.tracks.filter(isTrack)),
|
|
13330
|
+
baseline: new Map((log.externalFiles ?? []).map((f) => [f.path, f.sha256])),
|
|
13212
13331
|
refreshOnly: true,
|
|
13213
13332
|
installedByRecord: true,
|
|
13214
|
-
shared: { portions:
|
|
13333
|
+
shared: { portions: log.portions ?? [], excluded: [...excludedIds(log)] },
|
|
13334
|
+
log
|
|
13215
13335
|
});
|
|
13216
13336
|
}
|
|
13217
13337
|
function withRerender(next, r) {
|
|
@@ -13260,65 +13380,197 @@ function settleCliLog(ctx, target, remaining, done, anchorRemoved) {
|
|
|
13260
13380
|
delete next.policyFiles;
|
|
13261
13381
|
delete next.skillFiles;
|
|
13262
13382
|
}
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13383
|
+
return forgetRemoved(next, done);
|
|
13384
|
+
}
|
|
13385
|
+
function forgetRemoved(next, done) {
|
|
13386
|
+
const out = { ...next };
|
|
13387
|
+
const kept = (prefix) => (f) => !done.forgotten.has(`${prefix}${f.path}`);
|
|
13388
|
+
if (out.policyFiles !== void 0) out.policyFiles = out.policyFiles.filter(kept(CLAUDE_DIR3));
|
|
13389
|
+
if (out.skillFiles !== void 0) out.skillFiles = out.skillFiles.filter(kept(SKILLS_DIR3));
|
|
13390
|
+
const survivors = (out.externalFiles ?? []).filter(kept(""));
|
|
13391
|
+
if (survivors.length > 0) out.externalFiles = survivors;
|
|
13392
|
+
else delete out.externalFiles;
|
|
13393
|
+
if (out.rootFiles !== void 0) out.rootFiles = withoutRestored(out.rootFiles, done.displaced);
|
|
13269
13394
|
const touched = new Set(done.shared.map((r) => r.path));
|
|
13270
13395
|
const portions = [
|
|
13271
|
-
...(
|
|
13396
|
+
...(out.portions ?? []).filter((p2) => !touched.has(p2.path)),
|
|
13272
13397
|
...done.shared.flatMap((r) => r.portions)
|
|
13273
13398
|
];
|
|
13274
|
-
if (portions.length > 0)
|
|
13275
|
-
else delete
|
|
13276
|
-
return
|
|
13399
|
+
if (portions.length > 0) out.portions = portions;
|
|
13400
|
+
else delete out.portions;
|
|
13401
|
+
return out;
|
|
13277
13402
|
}
|
|
13278
|
-
function
|
|
13279
|
-
const target = resolveCliTarget(ctx, io);
|
|
13280
|
-
if (target === null) return;
|
|
13403
|
+
function runScopedRemoval(ctx, io, plan) {
|
|
13281
13404
|
const { options, installLog, projectDir, harnessRoot } = ctx;
|
|
13282
|
-
const
|
|
13283
|
-
const remaining =
|
|
13284
|
-
const
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
}
|
|
13405
|
+
const preview = options.dryRun === true;
|
|
13406
|
+
const remaining = plan.remaining.join(", ");
|
|
13407
|
+
const recordNote = `${plan.flag === "cli" ? "clis" : "tracks"}: ${remaining}`;
|
|
13408
|
+
const header = [
|
|
13409
|
+
"",
|
|
13410
|
+
c.bold(`uzys-agent-harness \xB7 uninstall --${plan.flag} ${plan.target}`),
|
|
13411
|
+
"",
|
|
13412
|
+
c.dim(` installed: ${plan.installed.join(", ")}`),
|
|
13413
|
+
c.dim(` removing: ${plan.target}`),
|
|
13414
|
+
c.dim(` remaining: ${remaining}`),
|
|
13415
|
+
""
|
|
13416
|
+
];
|
|
13417
|
+
for (const line of header) io.log(line);
|
|
13291
13418
|
const done = removeHarnessFiles(installLog, projectDir, plan.scope, {
|
|
13292
|
-
rm:
|
|
13293
|
-
|
|
13419
|
+
rm: preview ? () => {
|
|
13420
|
+
} : io.rm,
|
|
13421
|
+
write: !preview,
|
|
13294
13422
|
harnessRoot
|
|
13295
13423
|
});
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
if (
|
|
13303
|
-
|
|
13424
|
+
const what = plan.flag === "cli" ? "CLI" : "\uD2B8\uB799";
|
|
13425
|
+
const lines2 = [
|
|
13426
|
+
...preview ? [c.yellow(`[DRY RUN] ${what} \uC81C\uAC70 \uBBF8\uB9AC\uBCF4\uAE30 (\uC2E4\uC81C \uBCC0\uACBD \uC5C6\uC74C):`), ""] : [],
|
|
13427
|
+
...harnessRemovalLines(done, installLog, projectDir, plan.scope, preview),
|
|
13428
|
+
...plan.leftLines(preview, done)
|
|
13429
|
+
];
|
|
13430
|
+
if (preview) {
|
|
13431
|
+
if (plan.rerenderSet !== null) {
|
|
13432
|
+
lines2.push(
|
|
13433
|
+
` \u25CB would re-render AGENTS.md for ${displayNames(plan.rerenderSet)} (your sections kept)`
|
|
13434
|
+
);
|
|
13435
|
+
}
|
|
13436
|
+
for (const line of [...lines2, ` \u25CB install log updated (${recordNote})`, ""]) io.log(line);
|
|
13437
|
+
io.exit(0);
|
|
13438
|
+
return;
|
|
13439
|
+
}
|
|
13440
|
+
for (const line of lines2) io.log(line);
|
|
13441
|
+
const settled = plan.settle(done);
|
|
13442
|
+
const agentsBefore = readTextOrEmpty(join35(projectDir, AGENTS_MD2));
|
|
13443
|
+
const rerender = plan.rerenderSet === null ? null : rerenderAgentsMd(ctx, plan.rerenderSet, settled);
|
|
13444
|
+
const rewrote = rerender !== null && readTextOrEmpty(join35(projectDir, AGENTS_MD2)) !== agentsBefore;
|
|
13445
|
+
if (rewrote) for (const line of rerenderLines(rerender)) io.log(line);
|
|
13304
13446
|
const next = rerender === null ? settled : withRerender(settled, rerender);
|
|
13305
13447
|
try {
|
|
13306
13448
|
io.writeLog(projectDir, next);
|
|
13307
|
-
io.log(` ${status.success(`install log updated (
|
|
13449
|
+
io.log(` ${status.success(`install log updated (${recordNote})`)}`);
|
|
13308
13450
|
} catch (e2) {
|
|
13309
13451
|
io.err(status.failure(c.red(`ERROR: install log \uAC31\uC2E0 \uC2E4\uD328 \u2014 ${installLogPath(projectDir)}`)));
|
|
13310
13452
|
io.err(c.dim(` ${e2 instanceof Error ? e2.message : String(e2)}`));
|
|
13311
13453
|
io.log("");
|
|
13312
13454
|
io.log(
|
|
13313
|
-
c.yellow(
|
|
13455
|
+
c.yellow(
|
|
13456
|
+
`${plan.target} \uB294 \uC81C\uAC70\uB410\uC73C\uB098 install log \uB97C \uAC31\uC2E0\uD558\uC9C0 \uBABB\uD588\uB2E4 (\uAE30\uB85D\uC774 \uC2E4\uC81C\uC640 \uB2E4\uB974\uB2E4)`
|
|
13457
|
+
)
|
|
13314
13458
|
);
|
|
13315
13459
|
io.exit(1);
|
|
13316
13460
|
return;
|
|
13317
13461
|
}
|
|
13318
13462
|
io.log("");
|
|
13319
|
-
io.log(status.success(c.green(`${target} removed (remaining: ${remaining
|
|
13463
|
+
io.log(status.success(c.green(`${plan.target} removed (remaining: ${remaining})`)));
|
|
13320
13464
|
io.exit(0);
|
|
13321
13465
|
}
|
|
13466
|
+
function removeCliAction(ctx, io) {
|
|
13467
|
+
const { options, installLog, projectDir } = ctx;
|
|
13468
|
+
const installed = installedClis(installLog);
|
|
13469
|
+
const target = resolveScopeTarget(io, {
|
|
13470
|
+
flag: "cli",
|
|
13471
|
+
value: options.cli,
|
|
13472
|
+
conflict: options.only !== void 0,
|
|
13473
|
+
vocab: CLI_BASES,
|
|
13474
|
+
installed
|
|
13475
|
+
});
|
|
13476
|
+
if (target === null) return;
|
|
13477
|
+
const remaining = installed.filter((cli) => cli !== target);
|
|
13478
|
+
const plan = planCliRemoval(target, remaining, installLog, projectDir);
|
|
13479
|
+
runScopedRemoval(ctx, io, {
|
|
13480
|
+
flag: "cli",
|
|
13481
|
+
target,
|
|
13482
|
+
installed,
|
|
13483
|
+
remaining,
|
|
13484
|
+
scope: plan.scope,
|
|
13485
|
+
leftLines: (preview) => [
|
|
13486
|
+
...keptForRemainingLines(plan, preview),
|
|
13487
|
+
...assetsLeftLines(installLog, projectDir, plan.scope)
|
|
13488
|
+
],
|
|
13489
|
+
rerenderSet: agentsMdRerenderSet(target, remaining, installLog, projectDir),
|
|
13490
|
+
settle: (done) => settleCliLog(ctx, target, remaining, done, done.anchor.removed)
|
|
13491
|
+
});
|
|
13492
|
+
}
|
|
13493
|
+
function removeTrackAction(ctx, io) {
|
|
13494
|
+
const { options, installLog, projectDir } = ctx;
|
|
13495
|
+
const target = resolveScopeTarget(io, {
|
|
13496
|
+
flag: "track",
|
|
13497
|
+
value: options.track,
|
|
13498
|
+
conflict: options.only !== void 0 || options.cli !== void 0,
|
|
13499
|
+
vocab: TRACKS,
|
|
13500
|
+
installed: installLog.spec.tracks.filter(isTrack)
|
|
13501
|
+
});
|
|
13502
|
+
if (target === null) return;
|
|
13503
|
+
const removal = trackRemoval(installLog, target);
|
|
13504
|
+
runScopedRemoval(ctx, io, {
|
|
13505
|
+
flag: "track",
|
|
13506
|
+
target,
|
|
13507
|
+
installed: removal.known,
|
|
13508
|
+
remaining: removal.remaining,
|
|
13509
|
+
scope: { ...TRACK_SCOPE, has: removal.has },
|
|
13510
|
+
leftLines: (preview, done) => preview ? trackNotes(ctx, removal, done, false).map((l) => ` \u25CB ${l}`) : trackNotes(ctx, removal, done, true),
|
|
13511
|
+
rerenderSet: agentsMdRerenderSet(null, installedClis(installLog), installLog, projectDir),
|
|
13512
|
+
settle: (done) => settleTrackLog(ctx, target, removal.remaining, done)
|
|
13513
|
+
});
|
|
13514
|
+
}
|
|
13515
|
+
var TRACK_SCOPE = { shared: [], skeleton: [], folders: [], importBlock: false, anchor: false };
|
|
13516
|
+
function trackNotes(ctx, r, d2, write) {
|
|
13517
|
+
const md = join35(ctx.projectDir, "CLAUDE.md");
|
|
13518
|
+
const block = rootBlockNext(ctx, new Set(d2.removed));
|
|
13519
|
+
if (write && block !== null) {
|
|
13520
|
+
backupIfLossyUtf8(md);
|
|
13521
|
+
writeFileSync9(md, block);
|
|
13522
|
+
}
|
|
13523
|
+
const left = trackLeftLines(ctx.installLog, r, ctx.harnessRoot);
|
|
13524
|
+
if (write) {
|
|
13525
|
+
const refreshed = block === null ? [] : ["CLAUDE.md \u2014 harness block refreshed (\uBCF8\uBB38 \uBCF4\uC874)"];
|
|
13526
|
+
return [
|
|
13527
|
+
...refreshed.map((l) => ` ${status.success(l)}`),
|
|
13528
|
+
...left.map((l) => ` \u2298 left ${l}`)
|
|
13529
|
+
];
|
|
13530
|
+
}
|
|
13531
|
+
const would = block === null ? [] : ["would refresh the harness block in CLAUDE.md (\uBCF8\uBB38 \uBCF4\uC874)"];
|
|
13532
|
+
return [trackRemovalNames(d2.removed, r.target), ...would, ...left.map((l) => `leave ${l}`)];
|
|
13533
|
+
}
|
|
13534
|
+
function previewTrackRemoval(ctx, target) {
|
|
13535
|
+
const r = trackRemoval(ctx.installLog, target);
|
|
13536
|
+
const io = { rm: () => {
|
|
13537
|
+
}, write: false, harnessRoot: ctx.harnessRoot };
|
|
13538
|
+
const scope = { ...TRACK_SCOPE, has: r.has };
|
|
13539
|
+
const done = removeHarnessFiles(ctx.installLog, ctx.projectDir, scope, io);
|
|
13540
|
+
const counts = harnessRemovalLines(done, ctx.installLog, ctx.projectDir, scope, true);
|
|
13541
|
+
return [...counts.map((l) => l.replace(/^\s*○ /, "")), ...trackNotes(ctx, r, done, false)];
|
|
13542
|
+
}
|
|
13543
|
+
function rootBlockNext({ installLog, projectDir }, gone) {
|
|
13544
|
+
const anchor = installLog.templates?.rootClaudeMd?.path;
|
|
13545
|
+
const md = join35(projectDir, "CLAUDE.md");
|
|
13546
|
+
const text = safeExists(md) && outsideProjectTarget(projectDir, md) === null ? readTextOrEmpty(md) : "";
|
|
13547
|
+
if (anchor === void 0 || !safeExists(join35(projectDir, anchor))) return null;
|
|
13548
|
+
if (stripHarnessImport(text) === null) return null;
|
|
13549
|
+
const stays = (id) => listFilesRecursive(join35(projectDir, SKILLS_DIR3, id)).some(
|
|
13550
|
+
(f) => !gone.has(`${SKILLS_DIR3}${id}/${f}`)
|
|
13551
|
+
);
|
|
13552
|
+
const continuousSkills = CONTINUOUS_SKILLS.map((s) => s.id).filter(stays);
|
|
13553
|
+
const next = upsertHarnessImport(text, { projectName: "", tracks: [], continuousSkills });
|
|
13554
|
+
return next === text ? null : next;
|
|
13555
|
+
}
|
|
13556
|
+
function settleTrackLog(ctx, target, remaining, done) {
|
|
13557
|
+
const { installLog, projectDir } = ctx;
|
|
13558
|
+
const tracks = installLog.spec.tracks.filter((t) => t !== target);
|
|
13559
|
+
const next = forgetRemoved({ ...installLog, spec: { ...installLog.spec, tracks } }, done);
|
|
13560
|
+
const abs = join35(projectDir, INSTALLED_TRACKS);
|
|
13561
|
+
const key = INSTALLED_TRACKS.slice(CLAUDE_DIR3.length);
|
|
13562
|
+
const prior = installLog.policyFiles?.find((f) => f.path === key)?.sha256;
|
|
13563
|
+
const edited = prior !== void 0 && safeExists(abs) && hashContent(readTextOrEmpty(abs)) !== prior;
|
|
13564
|
+
if (!installedClis(installLog).includes("claude") || edited) return next;
|
|
13565
|
+
if (outsideProjectTarget(projectDir, abs) !== null) return next;
|
|
13566
|
+
const content = installedTracksText(remaining);
|
|
13567
|
+
writeFileSync9(abs, content);
|
|
13568
|
+
if (next.policyFiles !== void 0) {
|
|
13569
|
+
const sha256 = hashContent(content);
|
|
13570
|
+
next.policyFiles = [...next.policyFiles.filter((f) => f.path !== key), { path: key, sha256 }];
|
|
13571
|
+
}
|
|
13572
|
+
return next;
|
|
13573
|
+
}
|
|
13322
13574
|
var UNINSTALL_SHARED = Object.keys(SHARED_FILES).filter(
|
|
13323
13575
|
(p2) => p2 !== "CLAUDE.md"
|
|
13324
13576
|
);
|
|
@@ -13561,11 +13813,16 @@ var LEGACY_LOG = `${LEGACY_INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`;
|
|
|
13561
13813
|
var SKILLS_DIR3 = ".claude/skills/";
|
|
13562
13814
|
var CLI_FOLDERS2 = [".claude", ".codex", ".opencode"];
|
|
13563
13815
|
var LEFT_FILES_SHOWN = 3;
|
|
13816
|
+
var RECORD_FILES = [
|
|
13817
|
+
`${INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`,
|
|
13818
|
+
...HARNESS_RUNTIME_IGNORES
|
|
13819
|
+
];
|
|
13564
13820
|
var FULL_SCOPE = {
|
|
13565
13821
|
has: () => true,
|
|
13566
13822
|
shared: UNINSTALL_SHARED,
|
|
13567
13823
|
skeleton: PROJECT_SKELETON_DIRS,
|
|
13568
|
-
|
|
13824
|
+
// #817 — 기록 폴더도 — 헬퍼는 파일 하나씩 회수하고(#812) 설치자 파일은 남으니, 폴더가 왜 남았는지 같은 줄로 말한다
|
|
13825
|
+
folders: [...CLI_FOLDERS2, INSTALL_LOG_DIR],
|
|
13569
13826
|
importBlock: true,
|
|
13570
13827
|
anchor: true
|
|
13571
13828
|
};
|
|
@@ -13722,7 +13979,7 @@ function removeRecordedFile(path, ctx) {
|
|
|
13722
13979
|
if (verdict.verdict !== "remove" && verdict.verdict !== "backup+remove") return;
|
|
13723
13980
|
let backup = null;
|
|
13724
13981
|
if (verdict.verdict === "backup+remove") {
|
|
13725
|
-
backup = io.write ?
|
|
13982
|
+
backup = io.write ? relative8(projectDir, backupFile(effective)).split(sep8).join("/") : `${relative8(projectDir, effective).split(sep8).join("/")}.backup-<time>`;
|
|
13726
13983
|
}
|
|
13727
13984
|
if (io.write) {
|
|
13728
13985
|
if (effective !== abs) io.rm(abs);
|
|
@@ -13742,7 +13999,7 @@ function legacyFileCandidates() {
|
|
|
13742
13999
|
}
|
|
13743
14000
|
function mayReclaimLegacy(log, projectDir, path) {
|
|
13744
14001
|
const clis = installedClis(log);
|
|
13745
|
-
if (!
|
|
14002
|
+
if (!usersOf(path, projectDir).some((c3) => clis.includes(c3))) return false;
|
|
13746
14003
|
if (isExcludedPath(bundlePath(path), excludedIds(log))) return false;
|
|
13747
14004
|
return !(log.rootFiles ?? []).some(
|
|
13748
14005
|
(f) => f.path === path && f.change === "displaced" && !isLegacyDisplaced(log, projectDir, f)
|
|
@@ -14025,7 +14282,7 @@ function leftFolderLines(r, log, projectDir, scope, preview) {
|
|
|
14025
14282
|
const entries = /* @__PURE__ */ new Map();
|
|
14026
14283
|
for (const n of yoursUnder(projectDir, dir)) {
|
|
14027
14284
|
const full = `${dir}/${n.name}`;
|
|
14028
|
-
if (full === LEGACY_LOG) continue;
|
|
14285
|
+
if (full === LEGACY_LOG || RECORD_FILES.includes(full)) continue;
|
|
14029
14286
|
if (!gone.has(full) && !assetFiles.has(full) && !said(full)) entries.set(n.name, n.shown);
|
|
14030
14287
|
}
|
|
14031
14288
|
for (const p2 of back)
|
|
@@ -14125,7 +14382,7 @@ function removeAnchor(log, projectDir, io) {
|
|
|
14125
14382
|
]
|
|
14126
14383
|
};
|
|
14127
14384
|
}
|
|
14128
|
-
const backup = edited ?
|
|
14385
|
+
const backup = edited ? relative8(projectDir, backupFile(abs)).split(sep8).join("/") : null;
|
|
14129
14386
|
io.rm(abs);
|
|
14130
14387
|
return {
|
|
14131
14388
|
removed: true,
|
|
@@ -14188,7 +14445,7 @@ function removeLegacyAgentsMd(path, abs, effective, strip, ctx) {
|
|
|
14188
14445
|
let backup = `${path}.backup-<time>`;
|
|
14189
14446
|
try {
|
|
14190
14447
|
if (io.write) {
|
|
14191
|
-
backup =
|
|
14448
|
+
backup = relative8(projectDir, backupFile(effective)).split(sep8).join("/");
|
|
14192
14449
|
if (strip.remainder !== null) {
|
|
14193
14450
|
writeFileSync9(effective, neutralizeScaffoldBanner(strip.remainder), "utf8");
|
|
14194
14451
|
} else {
|
|
@@ -14234,7 +14491,7 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
14234
14491
|
try {
|
|
14235
14492
|
if (hashContent(readFileSync27(abs, "utf8")) !== sha256) {
|
|
14236
14493
|
const backup = backupFile(abs);
|
|
14237
|
-
notes.push(`backed up ${path} \u2192 ${
|
|
14494
|
+
notes.push(`backed up ${path} \u2192 ${relative8(projectDir, backup)} (changed since install)`);
|
|
14238
14495
|
}
|
|
14239
14496
|
rm(abs);
|
|
14240
14497
|
removed.push(path);
|
|
@@ -14249,8 +14506,8 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
14249
14506
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
14250
14507
|
let cursor = startDir;
|
|
14251
14508
|
while (true) {
|
|
14252
|
-
const rel =
|
|
14253
|
-
if (rel === "" || rel.startsWith(`..${
|
|
14509
|
+
const rel = relative8(projectDir, cursor);
|
|
14510
|
+
if (rel === "" || rel.startsWith(`..${sep8}`) || rel === "..") return;
|
|
14254
14511
|
try {
|
|
14255
14512
|
if (readdirSync7(cursor).length > 0) return;
|
|
14256
14513
|
rmdirSync3(cursor);
|
|
@@ -14305,6 +14562,9 @@ function registerUninstallCommand(cli) {
|
|
|
14305
14562
|
).option(
|
|
14306
14563
|
"--cli <name>",
|
|
14307
14564
|
`[Scope] Remove one CLI only (${CLI_BASES.join(" | ")}). Shared files stay until the last user leaves`
|
|
14565
|
+
).option(
|
|
14566
|
+
"--track <name>",
|
|
14567
|
+
`[Scope] Remove one track only (${TRACKS.join(" | ")}). Files another recorded track also installs stay`
|
|
14308
14568
|
).option("--yes", "[Mode] Skip the interactive picker and remove everything (non-interactive)").action(async (options) => {
|
|
14309
14569
|
await dispatchUninstall(options);
|
|
14310
14570
|
});
|
|
@@ -14313,7 +14573,7 @@ function shouldRunInteractive(options, isTty) {
|
|
|
14313
14573
|
if (!isTty) return false;
|
|
14314
14574
|
if (options.yes || options.dryRun) return false;
|
|
14315
14575
|
if (options.keepTemplates) return false;
|
|
14316
|
-
return options.only === void 0 && options.cli === void 0;
|
|
14576
|
+
return options.only === void 0 && options.cli === void 0 && options.track === void 0;
|
|
14317
14577
|
}
|
|
14318
14578
|
async function dispatchUninstall(options) {
|
|
14319
14579
|
const projectDir = resolve4(options.projectDir ?? process.cwd());
|
|
@@ -14326,7 +14586,7 @@ async function dispatchUninstall(options) {
|
|
|
14326
14586
|
console.error(
|
|
14327
14587
|
status.failure(
|
|
14328
14588
|
c.red(
|
|
14329
|
-
"ERROR: no terminal and no flag saying what to remove \u2014 nothing was done. Pass --yes (remove everything), --only <ids>, --cli <name>, or --dry-run."
|
|
14589
|
+
"ERROR: no terminal and no flag saying what to remove \u2014 nothing was done. Pass --yes (remove everything), --only <ids>, --cli <name>, --track <name>, or --dry-run."
|
|
14330
14590
|
)
|
|
14331
14591
|
)
|
|
14332
14592
|
);
|
|
@@ -14339,7 +14599,7 @@ async function dispatchUninstall(options) {
|
|
|
14339
14599
|
await runUninstallScreen(projectDir);
|
|
14340
14600
|
}
|
|
14341
14601
|
function lacksRemovalIntent(options) {
|
|
14342
|
-
return options.yes === void 0 && options.dryRun === void 0 && options.only === void 0 && options.cli === void 0;
|
|
14602
|
+
return options.yes === void 0 && options.dryRun === void 0 && options.only === void 0 && options.cli === void 0 && options.track === void 0;
|
|
14343
14603
|
}
|
|
14344
14604
|
async function runUninstallScreen(projectDir, opts = {}) {
|
|
14345
14605
|
const picked = await runInteractiveUninstall(projectDir, opts);
|