@uzysjung/agent-harness 26.171.0 → 26.173.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/README.ko.md +5 -3
- package/README.md +5 -3
- package/dist/{chunk-U4X4Z333.js → chunk-JABFWW6X.js} +30 -7
- package/dist/chunk-JABFWW6X.js.map +1 -0
- package/dist/index.js +361 -286
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/templates/CLAUDE.md +3 -1
- package/templates/rules/htmx-fastapi-ui.md +36 -0
- package/templates/rules/htmx-fastify-ui.md +9 -0
- package/templates/rules/ship-checklist.md +2 -2
- package/templates/rules/test-policy.md +7 -4
- package/templates/skills/audit-harness-fit/references/apply.md +6 -1
- package/templates/skills/audit-harness-fit/references/verification.md +5 -0
- package/templates/skills/objective-brief/SKILL.md +3 -1
- package/templates/track-mcp-map.tsv +1 -1
- package/dist/chunk-U4X4Z333.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -101,7 +101,7 @@ import {
|
|
|
101
101
|
targetsInclude,
|
|
102
102
|
upsertHarnessImport,
|
|
103
103
|
writeInstallLog
|
|
104
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-JABFWW6X.js";
|
|
105
105
|
|
|
106
106
|
// node_modules/sisteransi/src/index.js
|
|
107
107
|
var require_src = __commonJS({
|
|
@@ -3713,8 +3713,8 @@ function padDisplay(s, width) {
|
|
|
3713
3713
|
|
|
3714
3714
|
// src/install-writes.ts
|
|
3715
3715
|
init_esm_shims();
|
|
3716
|
-
import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
3717
|
-
import { dirname, join as
|
|
3716
|
+
import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3717
|
+
import { dirname, join as join8, relative, sep } from "path";
|
|
3718
3718
|
|
|
3719
3719
|
// src/env-files.ts
|
|
3720
3720
|
init_esm_shims();
|
|
@@ -3804,7 +3804,8 @@ var LINES = {
|
|
|
3804
3804
|
newInRelease: "wrote (new in this release)",
|
|
3805
3805
|
refreshed: "refreshed",
|
|
3806
3806
|
sameAsHarness: "kept \u2014 already the same as the harness version (left yours in place)",
|
|
3807
|
-
|
|
3807
|
+
// 비켜 둔 설치자 파일은 하네스가 그 자리를 떠날 때(uninstall · 회수) 되돌린다(`displaced` · #782) — 그 약속을 같은 줄에
|
|
3808
|
+
hadFile: "had a file with this name \u2014 saved as <file>.backup-<time> \u2014 yours, put back on uninstall",
|
|
3808
3809
|
noChecksum: "no checksum on record \u2014 saved a copy once",
|
|
3809
3810
|
edited: "you edited it \u2014 saved as <file>.backup-<time>",
|
|
3810
3811
|
removed: "removed",
|
|
@@ -3817,6 +3818,9 @@ var LINES = {
|
|
|
3817
3818
|
scaffoldKept: "kept \u2014 already there (yours)",
|
|
3818
3819
|
leftForYou: "left for you (yours now)"
|
|
3819
3820
|
};
|
|
3821
|
+
function withBackup(line, backup) {
|
|
3822
|
+
return line.replace("<file>.backup-<time>", backup);
|
|
3823
|
+
}
|
|
3820
3824
|
function unreadable(adapter, op) {
|
|
3821
3825
|
const what = op === "write" ? "harness part not added" : "harness part not removed";
|
|
3822
3826
|
return `could not read it (${ADAPTERS[adapter].unreadable}) \u2014 ${what}`;
|
|
@@ -3935,7 +3939,8 @@ var PRIOR_MCP_SERVERS = [
|
|
|
3935
3939
|
// #766 — `@supabase/mcp-server` 는 npm 에 없는 이름이라(404) 서버가 뜬 적이 없다
|
|
3936
3940
|
name: "supabase",
|
|
3937
3941
|
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"
|
|
3942
|
+
updated: "now Supabase's remote server \u2014 sign in on first use, see NEXT",
|
|
3943
|
+
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
3944
|
},
|
|
3940
3945
|
{
|
|
3941
3946
|
name: "chrome-devtools",
|
|
@@ -4116,7 +4121,7 @@ function recordedMcpAssetIds(log) {
|
|
|
4116
4121
|
// src/recorded.ts
|
|
4117
4122
|
init_esm_shims();
|
|
4118
4123
|
import { existsSync as existsSync5 } from "fs";
|
|
4119
|
-
import { join as
|
|
4124
|
+
import { join as join7 } from "path";
|
|
4120
4125
|
|
|
4121
4126
|
// src/cli-ownership.ts
|
|
4122
4127
|
init_esm_shims();
|
|
@@ -4171,6 +4176,42 @@ function removableFor(cli, remaining) {
|
|
|
4171
4176
|
return { exclusive, sharedNowUnowned };
|
|
4172
4177
|
}
|
|
4173
4178
|
|
|
4179
|
+
// src/foreign-slot.ts
|
|
4180
|
+
init_esm_shims();
|
|
4181
|
+
import { lstatSync, realpathSync, statSync } from "fs";
|
|
4182
|
+
import { join as join6 } from "path";
|
|
4183
|
+
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
4184
|
+
function occupiedByNonDirectory(path) {
|
|
4185
|
+
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
4186
|
+
return stat !== void 0 && !stat.isDirectory();
|
|
4187
|
+
}
|
|
4188
|
+
function linksToProjectSharedSkill(projectDir, id) {
|
|
4189
|
+
const slot = join6(projectDir, ".claude/skills", id);
|
|
4190
|
+
if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
|
|
4191
|
+
try {
|
|
4192
|
+
const real = realpathSync(slot);
|
|
4193
|
+
return real === join6(realpathSync(projectDir), ".agents/skills", id) && statSync(real).isDirectory();
|
|
4194
|
+
} catch {
|
|
4195
|
+
return false;
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
function foreignOwnedTarget(projectDir, rel) {
|
|
4199
|
+
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
4200
|
+
if (slot === void 0) return null;
|
|
4201
|
+
if (occupiedByNonDirectory(join6(projectDir, slot))) return slot;
|
|
4202
|
+
if (rel !== slot) {
|
|
4203
|
+
const parts = rel.slice(slot.length + 1).split("/");
|
|
4204
|
+
let cursor = slot;
|
|
4205
|
+
for (const part of parts.slice(0, -1)) {
|
|
4206
|
+
cursor = `${cursor}/${part}`;
|
|
4207
|
+
if (occupiedByNonDirectory(join6(projectDir, cursor))) return cursor;
|
|
4208
|
+
}
|
|
4209
|
+
const leaf = lstatSync(join6(projectDir, rel), { throwIfNoEntry: false });
|
|
4210
|
+
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
4211
|
+
}
|
|
4212
|
+
return null;
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4174
4215
|
// src/recorded.ts
|
|
4175
4216
|
var CLAUDE_DIR = ".claude/";
|
|
4176
4217
|
var ADVISORY_PATHS = [".env.example", ".github/workflows/", "skills-lock.json"];
|
|
@@ -4190,6 +4231,15 @@ function ownersOf(path) {
|
|
|
4190
4231
|
)
|
|
4191
4232
|
);
|
|
4192
4233
|
}
|
|
4234
|
+
function usersOf(path, projectDir) {
|
|
4235
|
+
const owners = ownersOf(path);
|
|
4236
|
+
const id = /^\.agents\/skills\/([^/]+)\//.exec(path)?.[1];
|
|
4237
|
+
const linked = id !== void 0 && linksToProjectSharedSkill(projectDir, id);
|
|
4238
|
+
return linked && !owners.includes("claude") ? [...owners, "claude"] : owners;
|
|
4239
|
+
}
|
|
4240
|
+
function bundlePath(path) {
|
|
4241
|
+
return path.startsWith(".agents/skills/") ? `${SKILLS_DIR}${path.slice(".agents/skills/".length)}` : path;
|
|
4242
|
+
}
|
|
4193
4243
|
function isRecordedTrackTarget(log, path) {
|
|
4194
4244
|
return recordedTrackTargets(log)(path);
|
|
4195
4245
|
}
|
|
@@ -4199,9 +4249,9 @@ function recordedTrackTargets(log) {
|
|
|
4199
4249
|
selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS
|
|
4200
4250
|
};
|
|
4201
4251
|
const entries = buildManifest(spec).filter((e2) => e2.applies(spec));
|
|
4202
|
-
const templates =
|
|
4252
|
+
const templates = join7(resolveBundleRoot(), "templates");
|
|
4203
4253
|
return (path) => entries.some(
|
|
4204
|
-
(e2) => e2.type === "dir" ? path.startsWith(`${e2.target}/`) && existsSync5(
|
|
4254
|
+
(e2) => e2.type === "dir" ? path.startsWith(`${e2.target}/`) && existsSync5(join7(templates, e2.source, path.slice(e2.target.length + 1))) : e2.target === path
|
|
4205
4255
|
);
|
|
4206
4256
|
}
|
|
4207
4257
|
function recordedTrackSpots(log) {
|
|
@@ -4209,13 +4259,13 @@ function recordedTrackSpots(log) {
|
|
|
4209
4259
|
tracks: log.spec.tracks.filter(isTrack),
|
|
4210
4260
|
selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS
|
|
4211
4261
|
};
|
|
4212
|
-
const templates =
|
|
4262
|
+
const templates = join7(resolveBundleRoot(), "templates");
|
|
4213
4263
|
const out = [];
|
|
4214
4264
|
for (const e2 of buildManifest(spec)) {
|
|
4215
4265
|
if (!e2.applies(spec) || !e2.target.startsWith(CLAUDE_DIR)) continue;
|
|
4216
4266
|
if (e2.type === "file") out.push(e2.target);
|
|
4217
4267
|
else
|
|
4218
|
-
for (const rel of listFilesRecursive(
|
|
4268
|
+
for (const rel of listFilesRecursive(join7(templates, e2.source)))
|
|
4219
4269
|
out.push(`${e2.target}/${rel}`);
|
|
4220
4270
|
}
|
|
4221
4271
|
for (const p2 of RETIRED_PATHS) out.push(`${CLAUDE_DIR}${p2}`);
|
|
@@ -4294,7 +4344,6 @@ function isScaffoldPath(path) {
|
|
|
4294
4344
|
// src/install-writes.ts
|
|
4295
4345
|
var CLAUDE_DIR2 = ".claude/";
|
|
4296
4346
|
var SKILLS_DIR2 = ".claude/skills/";
|
|
4297
|
-
var BACKUP_PLACEHOLDER = "<file>.backup-<time>";
|
|
4298
4347
|
function createInstallWriter(args) {
|
|
4299
4348
|
const { projectDir, previousLog, excluded } = args;
|
|
4300
4349
|
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
@@ -4319,16 +4368,17 @@ function createInstallWriter(args) {
|
|
|
4319
4368
|
else external.set(path, sha256);
|
|
4320
4369
|
}
|
|
4321
4370
|
function harness(path, from, opts = {}) {
|
|
4322
|
-
const abs =
|
|
4371
|
+
const abs = join8(projectDir, path);
|
|
4323
4372
|
if (outside.skip(abs)) return null;
|
|
4324
4373
|
const next = "source" in from ? readFileSync4(from.source, "utf8") : from.content;
|
|
4325
4374
|
const disk = existsSync6(abs) ? readFileSync4(abs, "utf8") : null;
|
|
4326
4375
|
const rec = recorded(previousLog, path, opts);
|
|
4327
4376
|
const diskSha = rec.state === "no-sha" && disk !== null ? hashContent(disk) : null;
|
|
4377
|
+
const judgedRec = diskSha !== null && isLegacyHarnessFile(path, diskSha) ? { state: "sha", sha256: diskSha } : rec;
|
|
4328
4378
|
const j4 = judge({
|
|
4329
4379
|
op: "write",
|
|
4330
4380
|
kind: "harness",
|
|
4331
|
-
rec:
|
|
4381
|
+
rec: judgedRec,
|
|
4332
4382
|
disk,
|
|
4333
4383
|
next,
|
|
4334
4384
|
...args.run === void 0 ? {} : { run: args.run }
|
|
@@ -4373,11 +4423,19 @@ function createInstallWriter(args) {
|
|
|
4373
4423
|
default:
|
|
4374
4424
|
throw new Error(`install: '${j4.record}' is not a write record (${path})`);
|
|
4375
4425
|
}
|
|
4376
|
-
const line = backup === void 0 ? j4.line : j4.line
|
|
4426
|
+
const line = backup === void 0 ? j4.line : withBackup(j4.line, backup);
|
|
4377
4427
|
const backupPair = backup === void 0 || backupAbs === void 0 ? {} : { backup, backupAbs };
|
|
4378
|
-
|
|
4428
|
+
const state = judgedRec.state;
|
|
4429
|
+
writes.push({
|
|
4430
|
+
path,
|
|
4431
|
+
verdict: j4.verdict,
|
|
4432
|
+
record: j4.record,
|
|
4433
|
+
recorded: state,
|
|
4434
|
+
line,
|
|
4435
|
+
...backupPair
|
|
4436
|
+
});
|
|
4379
4437
|
if (j4.line === "" || j4.verdict === "create" || j4.verdict === "overwrite") return null;
|
|
4380
|
-
const out = { path, verdict: j4.verdict, line, ...backupPair };
|
|
4438
|
+
const out = { path, verdict: j4.verdict, recorded: state, line, ...backupPair };
|
|
4381
4439
|
judged.push(out);
|
|
4382
4440
|
return out;
|
|
4383
4441
|
}
|
|
@@ -4385,7 +4443,7 @@ function createInstallWriter(args) {
|
|
|
4385
4443
|
const file = SHARED_FILES[path];
|
|
4386
4444
|
if (file === void 0) throw new Error(`install: ${path} is not a shared file`);
|
|
4387
4445
|
const adapter = ADAPTERS[file.adapter];
|
|
4388
|
-
const abs =
|
|
4446
|
+
const abs = join8(projectDir, path);
|
|
4389
4447
|
const disk = existsSync6(abs) ? readFileSync4(abs, "utf8") : null;
|
|
4390
4448
|
const own = new Map(
|
|
4391
4449
|
(previousLog?.portions ?? []).filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
|
|
@@ -4567,9 +4625,9 @@ function retiredClaims(previous, projectDir, kept) {
|
|
|
4567
4625
|
const out = [];
|
|
4568
4626
|
for (const p2 of RETIRED_PATHS) {
|
|
4569
4627
|
if (have.has(p2) || recorded(previous, `${CLAUDE_DIR2}${p2}`).state === "none") continue;
|
|
4570
|
-
const abs =
|
|
4628
|
+
const abs = join8(projectDir, CLAUDE_DIR2, p2);
|
|
4571
4629
|
try {
|
|
4572
|
-
if (!
|
|
4630
|
+
if (!statSync2(abs).isFile()) continue;
|
|
4573
4631
|
out.push({ path: p2, sha256: hashContent(readFileSync4(abs, "utf8")) });
|
|
4574
4632
|
} catch {
|
|
4575
4633
|
}
|
|
@@ -4674,7 +4732,7 @@ function legacySettingsSeed(text, render, projectDir, opts = {}) {
|
|
|
4674
4732
|
} catch {
|
|
4675
4733
|
return seed;
|
|
4676
4734
|
}
|
|
4677
|
-
const claudeDir =
|
|
4735
|
+
const claudeDir = join8(projectDir, ".claude");
|
|
4678
4736
|
const candidates = new Set([...render.keys()].filter((k2) => k2.startsWith("hooks.")));
|
|
4679
4737
|
const legacyLines = /* @__PURE__ */ new Set();
|
|
4680
4738
|
const hooks = isObject2(root) && isObject2(root.hooks) ? root.hooks : {};
|
|
@@ -4790,13 +4848,13 @@ function legacyGitignoreSeed(text, render, previous) {
|
|
|
4790
4848
|
|
|
4791
4849
|
// src/installer.ts
|
|
4792
4850
|
init_esm_shims();
|
|
4793
|
-
import { chmodSync as chmodSync3, existsSync as existsSync22, readdirSync as readdirSync5, readFileSync as
|
|
4794
|
-
import { basename as basename4, join as join31, relative as
|
|
4851
|
+
import { chmodSync as chmodSync3, existsSync as existsSync22, readdirSync as readdirSync5, readFileSync as readFileSync23, writeFileSync as writeFileSync8 } from "fs";
|
|
4852
|
+
import { basename as basename4, join as join31, relative as relative6, resolve, sep as sep7 } from "path";
|
|
4795
4853
|
|
|
4796
4854
|
// src/anchor-seed.ts
|
|
4797
4855
|
init_esm_shims();
|
|
4798
4856
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
4799
|
-
import { join as
|
|
4857
|
+
import { join as join9 } from "path";
|
|
4800
4858
|
|
|
4801
4859
|
// src/agents-md-render.ts
|
|
4802
4860
|
init_esm_shims();
|
|
@@ -4903,7 +4961,7 @@ function compose(body, into) {
|
|
|
4903
4961
|
${trimmed.join("\n")}`;
|
|
4904
4962
|
}
|
|
4905
4963
|
function seedAgentsMdProjectContext(projectDir) {
|
|
4906
|
-
const root = readIfExists(
|
|
4964
|
+
const root = readIfExists(join9(projectDir, "CLAUDE.md"));
|
|
4907
4965
|
if (root === null) return null;
|
|
4908
4966
|
const withoutImport = stripHarnessImport(root) ?? root;
|
|
4909
4967
|
const body = dropHarnessQuoteBlocks(dropTitle(withoutImport.split("\n")));
|
|
@@ -4911,9 +4969,9 @@ function seedAgentsMdProjectContext(projectDir) {
|
|
|
4911
4969
|
return compose(body, "agents-md");
|
|
4912
4970
|
}
|
|
4913
4971
|
function seedRootClaudeProjectContext(projectDir, harnessRoot) {
|
|
4914
|
-
const agents = readIfExists(
|
|
4972
|
+
const agents = readIfExists(join9(projectDir, "AGENTS.md"));
|
|
4915
4973
|
if (agents === null) return null;
|
|
4916
|
-
const template = readIfExists(
|
|
4974
|
+
const template = readIfExists(join9(harnessRoot, AGENTS_MD_TEMPLATE));
|
|
4917
4975
|
if (template === null) return null;
|
|
4918
4976
|
const installer = projectContextInstallerLines({ existing: agents, template });
|
|
4919
4977
|
if (installer === null) return null;
|
|
@@ -4925,7 +4983,7 @@ function seedRootClaudeProjectContext(projectDir, harnessRoot) {
|
|
|
4925
4983
|
// src/ci-scaffold.ts
|
|
4926
4984
|
init_esm_shims();
|
|
4927
4985
|
import { existsSync as existsSync8 } from "fs";
|
|
4928
|
-
import { join as
|
|
4986
|
+
import { join as join10 } from "path";
|
|
4929
4987
|
var PYTHON_TRACKS = "data|csr-fastapi|full";
|
|
4930
4988
|
var VARIANTS = [
|
|
4931
4989
|
{
|
|
@@ -4949,12 +5007,12 @@ function installCiScaffold(ctx) {
|
|
|
4949
5007
|
for (const v2 of VARIANTS) {
|
|
4950
5008
|
if (!v2.applies(ctx.tracks)) continue;
|
|
4951
5009
|
const relTarget = `.github/workflows/${v2.target}`;
|
|
4952
|
-
const target =
|
|
5010
|
+
const target = join10(ctx.projectDir, relTarget);
|
|
4953
5011
|
if (existsSync8(target)) {
|
|
4954
5012
|
report.skippedExisting.push(relTarget);
|
|
4955
5013
|
continue;
|
|
4956
5014
|
}
|
|
4957
|
-
copyFile(
|
|
5015
|
+
copyFile(join10(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
|
|
4958
5016
|
report.written.push(relTarget);
|
|
4959
5017
|
}
|
|
4960
5018
|
return report;
|
|
@@ -4972,7 +5030,7 @@ import { basename, join as join14 } from "path";
|
|
|
4972
5030
|
|
|
4973
5031
|
// src/agents-skill-targets.ts
|
|
4974
5032
|
init_esm_shims();
|
|
4975
|
-
import { join as
|
|
5033
|
+
import { join as join11, sep as sep2 } from "path";
|
|
4976
5034
|
function bundledSkillTargets(projectDir, log = readInstallLog(projectDir)) {
|
|
4977
5035
|
if (log === null) return [];
|
|
4978
5036
|
const tracks = log.spec.tracks.filter(
|
|
@@ -4986,7 +5044,7 @@ function bundledSkillTargets(projectDir, log = readInstallLog(projectDir)) {
|
|
|
4986
5044
|
}
|
|
4987
5045
|
function allowSkillCreation(writer, projectDir, ids) {
|
|
4988
5046
|
if (ids.length === 0) return writer;
|
|
4989
|
-
const roots = ids.map((id) => `${
|
|
5047
|
+
const roots = ids.map((id) => `${join11(projectDir, ".agents", "skills", id)}${sep2}`);
|
|
4990
5048
|
return {
|
|
4991
5049
|
write(absPath, content, opts) {
|
|
4992
5050
|
const inTarget = roots.some((root) => absPath.startsWith(root));
|
|
@@ -5009,48 +5067,11 @@ function agentsSkillSlot(params) {
|
|
|
5009
5067
|
init_esm_shims();
|
|
5010
5068
|
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
5011
5069
|
import { dirname as dirname2, relative as relative2, sep as sep3 } from "path";
|
|
5012
|
-
|
|
5013
|
-
// src/foreign-slot.ts
|
|
5014
|
-
init_esm_shims();
|
|
5015
|
-
import { lstatSync, realpathSync, statSync as statSync2 } from "fs";
|
|
5016
|
-
import { join as join11 } from "path";
|
|
5017
|
-
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
5018
|
-
function occupiedByNonDirectory(path) {
|
|
5019
|
-
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
5020
|
-
return stat !== void 0 && !stat.isDirectory();
|
|
5021
|
-
}
|
|
5022
|
-
function linksToProjectSharedSkill(projectDir, id) {
|
|
5023
|
-
const slot = join11(projectDir, ".claude/skills", id);
|
|
5024
|
-
if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
|
|
5025
|
-
try {
|
|
5026
|
-
const real = realpathSync(slot);
|
|
5027
|
-
return real === join11(realpathSync(projectDir), ".agents/skills", id) && statSync2(real).isDirectory();
|
|
5028
|
-
} catch {
|
|
5029
|
-
return false;
|
|
5030
|
-
}
|
|
5031
|
-
}
|
|
5032
|
-
function foreignOwnedTarget(projectDir, rel) {
|
|
5033
|
-
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
5034
|
-
if (slot === void 0) return null;
|
|
5035
|
-
if (occupiedByNonDirectory(join11(projectDir, slot))) return slot;
|
|
5036
|
-
if (rel !== slot) {
|
|
5037
|
-
const parts = rel.slice(slot.length + 1).split("/");
|
|
5038
|
-
let cursor = slot;
|
|
5039
|
-
for (const part of parts.slice(0, -1)) {
|
|
5040
|
-
cursor = `${cursor}/${part}`;
|
|
5041
|
-
if (occupiedByNonDirectory(join11(projectDir, cursor))) return cursor;
|
|
5042
|
-
}
|
|
5043
|
-
const leaf = lstatSync(join11(projectDir, rel), { throwIfNoEntry: false });
|
|
5044
|
-
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
5045
|
-
}
|
|
5046
|
-
return null;
|
|
5047
|
-
}
|
|
5048
|
-
|
|
5049
|
-
// src/owned-write.ts
|
|
5050
5070
|
function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
5051
5071
|
const { now = /* @__PURE__ */ new Date(), refreshOnly = false, journal, owned = () => false } = options;
|
|
5052
5072
|
const written = /* @__PURE__ */ new Map();
|
|
5053
5073
|
const backedUp = [];
|
|
5074
|
+
const judged = [];
|
|
5054
5075
|
const backupPaths = [];
|
|
5055
5076
|
const foreignOwned = [];
|
|
5056
5077
|
const restored = [];
|
|
@@ -5075,14 +5096,30 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
5075
5096
|
const disk = existsSync9(absPath) ? readFileSync6(absPath, "utf8") : null;
|
|
5076
5097
|
if (disk === null && refreshOnly && !opts?.createInRefresh && rec.state !== "sha")
|
|
5077
5098
|
return false;
|
|
5078
|
-
const { verdict, record } = judge({
|
|
5099
|
+
const { verdict, record, line } = judge({
|
|
5100
|
+
op: "write",
|
|
5101
|
+
kind: "harness",
|
|
5102
|
+
rec,
|
|
5103
|
+
disk,
|
|
5104
|
+
next: content
|
|
5105
|
+
});
|
|
5079
5106
|
let backupRel = null;
|
|
5080
5107
|
if (verdict === "backup+overwrite" || verdict === "overwrite" && opts?.backupFirst === true) {
|
|
5081
5108
|
const backup = backupFile(absPath, now);
|
|
5082
5109
|
backupPaths.push(backup);
|
|
5083
5110
|
journal?.backup(backup);
|
|
5084
5111
|
backupRel = relative2(projectDir, backup).split(sep3).join("/");
|
|
5085
|
-
if (verdict === "backup+overwrite"
|
|
5112
|
+
if (verdict === "backup+overwrite") {
|
|
5113
|
+
if (rec.state !== "none") backedUp.push(rel);
|
|
5114
|
+
judged.push({
|
|
5115
|
+
path: rel,
|
|
5116
|
+
verdict,
|
|
5117
|
+
recorded: rec.state,
|
|
5118
|
+
line: withBackup(line, backupRel),
|
|
5119
|
+
backup: backupRel,
|
|
5120
|
+
backupAbs: backup
|
|
5121
|
+
});
|
|
5122
|
+
}
|
|
5086
5123
|
}
|
|
5087
5124
|
if (record === "displaced" || record === "displaced+sha") {
|
|
5088
5125
|
displace({ path: rel, change: "displaced", notes: backupRel === null ? [] : [backupRel] });
|
|
@@ -5108,6 +5145,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
5108
5145
|
outside: outside.list(),
|
|
5109
5146
|
files: [...written].map(([path, sha256]) => ({ path, sha256 })),
|
|
5110
5147
|
backedUp: [...backedUp],
|
|
5148
|
+
judged: [...judged],
|
|
5111
5149
|
backupPaths: [...backupPaths],
|
|
5112
5150
|
updated,
|
|
5113
5151
|
restored: [...restored],
|
|
@@ -6237,6 +6275,7 @@ function runCliTransforms(params) {
|
|
|
6237
6275
|
const externalFiles = [];
|
|
6238
6276
|
const externalBackups = [];
|
|
6239
6277
|
const externalBackedUp = [];
|
|
6278
|
+
const externalJudged = [];
|
|
6240
6279
|
const externalForeignOwned = [];
|
|
6241
6280
|
const displaced = /* @__PURE__ */ new Map();
|
|
6242
6281
|
let externalOutside = [];
|
|
@@ -6258,6 +6297,7 @@ function runCliTransforms(params) {
|
|
|
6258
6297
|
}
|
|
6259
6298
|
externalBackups.push(...report.ownership.backupPaths);
|
|
6260
6299
|
externalBackedUp.push(...report.ownership.backedUp);
|
|
6300
|
+
externalJudged.push(...report.ownership.judged ?? []);
|
|
6261
6301
|
for (const f of report.ownership.displaced ?? [])
|
|
6262
6302
|
if (!displaced.has(f.path)) displaced.set(f.path, f);
|
|
6263
6303
|
for (const f of report.ownership.foreignOwned) {
|
|
@@ -6365,6 +6405,7 @@ function runCliTransforms(params) {
|
|
|
6365
6405
|
externalBackups,
|
|
6366
6406
|
externalUpdated,
|
|
6367
6407
|
externalBackedUp,
|
|
6408
|
+
externalJudged,
|
|
6368
6409
|
displaced: [...displaced.values()],
|
|
6369
6410
|
externalForeignOwned,
|
|
6370
6411
|
externalOutside,
|
|
@@ -6381,7 +6422,7 @@ function runCliTransforms(params) {
|
|
|
6381
6422
|
|
|
6382
6423
|
// src/displaced.ts
|
|
6383
6424
|
init_esm_shims();
|
|
6384
|
-
import { lstatSync as lstatSync3, mkdirSync as mkdirSync5, renameSync } from "fs";
|
|
6425
|
+
import { lstatSync as lstatSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync17, renameSync } from "fs";
|
|
6385
6426
|
import { dirname as dirname5, join as join23 } from "path";
|
|
6386
6427
|
function exists(abs) {
|
|
6387
6428
|
try {
|
|
@@ -6391,13 +6432,23 @@ function exists(abs) {
|
|
|
6391
6432
|
return false;
|
|
6392
6433
|
}
|
|
6393
6434
|
}
|
|
6435
|
+
function isLegacyDisplaced(log, projectDir, f) {
|
|
6436
|
+
if (f.change !== "displaced") return false;
|
|
6437
|
+
let original;
|
|
6438
|
+
try {
|
|
6439
|
+
original = readFileSync17(join23(projectDir, f.notes[0] ?? f.path), "utf8");
|
|
6440
|
+
} catch {
|
|
6441
|
+
return false;
|
|
6442
|
+
}
|
|
6443
|
+
return isLegacyHarnessFile(f.path, hashContent(original)) && !isRecordedTrackTarget(log, bundlePath(f.path));
|
|
6444
|
+
}
|
|
6394
6445
|
function restoreDisplaced(log, projectDir, opts) {
|
|
6395
6446
|
const out = [];
|
|
6396
6447
|
for (const f of log.rootFiles ?? []) {
|
|
6397
6448
|
if (f.change !== "displaced" || !opts.has(f.path)) continue;
|
|
6398
6449
|
const slot = join23(projectDir, f.path);
|
|
6399
6450
|
if (outsideProjectTarget(projectDir, slot) !== null) continue;
|
|
6400
|
-
const backup = f.notes[0] ?? null;
|
|
6451
|
+
const backup = isLegacyDisplaced(log, projectDir, f) ? null : f.notes[0] ?? null;
|
|
6401
6452
|
const d2 = judgeDisplaced({
|
|
6402
6453
|
backup,
|
|
6403
6454
|
backupExists: backup !== null && exists(join23(projectDir, backup)),
|
|
@@ -6466,7 +6517,7 @@ import {
|
|
|
6466
6517
|
constants as fsConstants,
|
|
6467
6518
|
mkdirSync as mkdirSync6,
|
|
6468
6519
|
readdirSync as readdirSync3,
|
|
6469
|
-
readFileSync as
|
|
6520
|
+
readFileSync as readFileSync18,
|
|
6470
6521
|
renameSync as renameSync2,
|
|
6471
6522
|
rmdirSync,
|
|
6472
6523
|
rmSync as rmSync3
|
|
@@ -6484,7 +6535,7 @@ function snapshotSkillRoots(projectDir) {
|
|
|
6484
6535
|
for (const rel of listFilesRecursive(join25(projectDir, root))) {
|
|
6485
6536
|
const path = `${root}/${rel}`;
|
|
6486
6537
|
try {
|
|
6487
|
-
out.set(path, hashContent(
|
|
6538
|
+
out.set(path, hashContent(readFileSync18(join25(projectDir, path), "utf8")));
|
|
6488
6539
|
} catch {
|
|
6489
6540
|
}
|
|
6490
6541
|
}
|
|
@@ -6677,14 +6728,15 @@ function refreshExternalSkills(projectDir, deps = {}) {
|
|
|
6677
6728
|
}
|
|
6678
6729
|
throw e2;
|
|
6679
6730
|
}
|
|
6680
|
-
const { report, displaced } = settle(ran);
|
|
6731
|
+
const { report, displaced, judged } = settle(ran);
|
|
6681
6732
|
recordRefreshedFiles(projectDir, log, report, displaced, deps.writeLog ?? writeInstallLog);
|
|
6682
6733
|
return {
|
|
6683
6734
|
attempted: report.attempted.length,
|
|
6684
6735
|
refreshed: report.succeeded,
|
|
6685
6736
|
failed: report.attempted.filter((r) => !r.ok).map((r) => ({ id: r.asset.id, message: r.message ?? "failed" })),
|
|
6686
6737
|
notInCatalog,
|
|
6687
|
-
unknown: false
|
|
6738
|
+
unknown: false,
|
|
6739
|
+
judged
|
|
6688
6740
|
};
|
|
6689
6741
|
}
|
|
6690
6742
|
function holdSkillSlots(projectDir, hold) {
|
|
@@ -6717,7 +6769,11 @@ function holdSkillSlots(projectDir, hold) {
|
|
|
6717
6769
|
hold.clis,
|
|
6718
6770
|
{ log: hold.log, scope: hold.scope }
|
|
6719
6771
|
);
|
|
6720
|
-
const out = {
|
|
6772
|
+
const out = {
|
|
6773
|
+
backups: [],
|
|
6774
|
+
displaced: [],
|
|
6775
|
+
judged: []
|
|
6776
|
+
};
|
|
6721
6777
|
const back = /* @__PURE__ */ new Set();
|
|
6722
6778
|
const failed = [];
|
|
6723
6779
|
for (const f of moved) {
|
|
@@ -6730,6 +6786,16 @@ function holdSkillSlots(projectDir, hold) {
|
|
|
6730
6786
|
}
|
|
6731
6787
|
out.backups.push(r.backup);
|
|
6732
6788
|
if (r.displaced !== null) out.displaced.push(r.displaced);
|
|
6789
|
+
const backup = toProjectRel(projectDir, r.backup);
|
|
6790
|
+
const line = !f.recorded ? LINES.hadFile : r.gone ? LINES.editedRemoved : LINES.edited;
|
|
6791
|
+
out.judged.push({
|
|
6792
|
+
path: f.rel,
|
|
6793
|
+
verdict: r.gone ? "backup+remove" : "backup+overwrite",
|
|
6794
|
+
recorded: f.recorded ? "sha" : "none",
|
|
6795
|
+
line: withBackup(line, backup),
|
|
6796
|
+
backup,
|
|
6797
|
+
backupAbs: r.backup
|
|
6798
|
+
});
|
|
6733
6799
|
} catch (e2) {
|
|
6734
6800
|
failed.push(`${f.rel} (${e2 instanceof Error ? e2.message : String(e2)})`);
|
|
6735
6801
|
}
|
|
@@ -6781,7 +6847,7 @@ function slotFilesToKeep(projectDir, hold) {
|
|
|
6781
6847
|
continue;
|
|
6782
6848
|
const abs = join25(projectDir, path);
|
|
6783
6849
|
try {
|
|
6784
|
-
if (recordedSha !== void 0 && hashContent(
|
|
6850
|
+
if (recordedSha !== void 0 && hashContent(readFileSync18(abs, "utf8")) === recordedSha)
|
|
6785
6851
|
continue;
|
|
6786
6852
|
accessSync(abs, fsConstants.R_OK);
|
|
6787
6853
|
} catch {
|
|
@@ -6795,21 +6861,22 @@ function slotFilesToKeep(projectDir, hold) {
|
|
|
6795
6861
|
function settleAside(projectDir, f, restore) {
|
|
6796
6862
|
let now = null;
|
|
6797
6863
|
try {
|
|
6798
|
-
now =
|
|
6864
|
+
now = readFileSync18(f.abs);
|
|
6799
6865
|
} catch (e2) {
|
|
6800
6866
|
const code = e2.code;
|
|
6801
6867
|
if (code !== "ENOENT" && code !== "ENOTDIR") throw e2;
|
|
6802
6868
|
}
|
|
6803
|
-
if (restore || (now === null ? !f.recorded : now.equals(
|
|
6869
|
+
if (restore || (now === null ? !f.recorded : now.equals(readFileSync18(f.aside)))) {
|
|
6804
6870
|
mkdirSync6(dirname6(f.abs), { recursive: true });
|
|
6805
6871
|
renameSync2(f.aside, f.abs);
|
|
6806
6872
|
return "back";
|
|
6807
6873
|
}
|
|
6808
6874
|
const backup = backupFromCopy(f.abs, f.aside);
|
|
6809
6875
|
rmSync3(f.aside, { force: true });
|
|
6810
|
-
if (f.recorded || now === null) return { backup, displaced: null };
|
|
6876
|
+
if (f.recorded || now === null) return { backup, gone: now === null, displaced: null };
|
|
6811
6877
|
return {
|
|
6812
6878
|
backup,
|
|
6879
|
+
gone: false,
|
|
6813
6880
|
displaced: { path: f.rel, change: "displaced", notes: [toProjectRel(projectDir, backup)] }
|
|
6814
6881
|
};
|
|
6815
6882
|
}
|
|
@@ -6935,7 +7002,7 @@ function detectVersion(method, spawn, scope, projectDir) {
|
|
|
6935
7002
|
case "npm": {
|
|
6936
7003
|
const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join25(projectDir, "node_modules", method.pkg, "package.json");
|
|
6937
7004
|
if (!pkgJson || !existsSync19(pkgJson)) return void 0;
|
|
6938
|
-
const parsed = JSON.parse(
|
|
7005
|
+
const parsed = JSON.parse(readFileSync18(pkgJson, "utf8"));
|
|
6939
7006
|
return parsed.version;
|
|
6940
7007
|
}
|
|
6941
7008
|
default:
|
|
@@ -6949,7 +7016,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
|
|
|
6949
7016
|
const claudeScope = scope === "global" ? "user" : "project";
|
|
6950
7017
|
const path = join25(homedir2(), ".claude/plugins/installed_plugins.json");
|
|
6951
7018
|
if (!existsSync19(path)) return void 0;
|
|
6952
|
-
const data = JSON.parse(
|
|
7019
|
+
const data = JSON.parse(readFileSync18(path, "utf8"));
|
|
6953
7020
|
const entries = data.plugins?.[pluginId] ?? [];
|
|
6954
7021
|
const matches = entries.filter(
|
|
6955
7022
|
(e2) => claudeScope === "user" ? e2.scope === "user" : e2.scope === "project" && e2.projectPath === projectDir
|
|
@@ -6981,7 +7048,7 @@ function getNpmGlobalRoot(spawn) {
|
|
|
6981
7048
|
|
|
6982
7049
|
// src/key-ids.ts
|
|
6983
7050
|
init_esm_shims();
|
|
6984
|
-
import { readFileSync as
|
|
7051
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
6985
7052
|
import { join as join26 } from "path";
|
|
6986
7053
|
function renderedKeyIds(harnessRoot, input, clis) {
|
|
6987
7054
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -7005,7 +7072,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
|
|
|
7005
7072
|
for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
|
|
7006
7073
|
for (const key of gitignoreRender().keys()) add(".gitignore", key);
|
|
7007
7074
|
if (clis.includes("claude")) {
|
|
7008
|
-
const template =
|
|
7075
|
+
const template = readFileSync19(join26(harnessRoot, "templates/settings.json"), "utf8");
|
|
7009
7076
|
for (const key of renderSettingsPortion(template, () => true).keys()) {
|
|
7010
7077
|
add(".claude/settings.json", key);
|
|
7011
7078
|
}
|
|
@@ -7061,6 +7128,8 @@ function refreshLinkedSkillBodies(params) {
|
|
|
7061
7128
|
notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
|
|
7062
7129
|
files: result.files,
|
|
7063
7130
|
backupPaths: result.backupPaths,
|
|
7131
|
+
backedUp: result.backedUp,
|
|
7132
|
+
judged: result.judged ?? [],
|
|
7064
7133
|
displaced: result.displaced ?? [],
|
|
7065
7134
|
foreignOwned: result.foreignOwned,
|
|
7066
7135
|
outside: result.outside ?? []
|
|
@@ -7069,7 +7138,7 @@ function refreshLinkedSkillBodies(params) {
|
|
|
7069
7138
|
|
|
7070
7139
|
// src/out-of-track.ts
|
|
7071
7140
|
init_esm_shims();
|
|
7072
|
-
import { lstatSync as lstatSync4, readFileSync as
|
|
7141
|
+
import { lstatSync as lstatSync4, readFileSync as readFileSync20, rmSync as rmSync4 } from "fs";
|
|
7073
7142
|
import { join as join28 } from "path";
|
|
7074
7143
|
var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
|
|
7075
7144
|
var ANCHOR_RULE = "uzys-harness";
|
|
@@ -7099,7 +7168,7 @@ function reclaimOutOfTrack(args) {
|
|
|
7099
7168
|
out.bringBack[f.path] = tracksBringing(name);
|
|
7100
7169
|
continue;
|
|
7101
7170
|
}
|
|
7102
|
-
const disk = stat === void 0 ? null :
|
|
7171
|
+
const disk = stat === void 0 ? null : readFileSync20(abs, "utf8");
|
|
7103
7172
|
const verdict = judge({
|
|
7104
7173
|
op: "remove",
|
|
7105
7174
|
kind: "harness",
|
|
@@ -7131,7 +7200,7 @@ function tracksBringing(rule) {
|
|
|
7131
7200
|
|
|
7132
7201
|
// src/state.ts
|
|
7133
7202
|
init_esm_shims();
|
|
7134
|
-
import { existsSync as existsSync20, readFileSync as
|
|
7203
|
+
import { existsSync as existsSync20, readFileSync as readFileSync21 } from "fs";
|
|
7135
7204
|
import { join as join29 } from "path";
|
|
7136
7205
|
var META_FILE = ".claude/.installed-tracks";
|
|
7137
7206
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
@@ -7160,7 +7229,7 @@ function tracksFromLog(tracks) {
|
|
|
7160
7229
|
}
|
|
7161
7230
|
function collectTraces(projectDir) {
|
|
7162
7231
|
const at5 = (rel) => join29(projectDir, rel);
|
|
7163
|
-
const contains = (rel, needle) => existsSync20(at5(rel)) &&
|
|
7232
|
+
const contains = (rel, needle) => existsSync20(at5(rel)) && readFileSync21(at5(rel), "utf8").includes(needle);
|
|
7164
7233
|
const traces = [];
|
|
7165
7234
|
if (existsSync20(at5(META_FILE)))
|
|
7166
7235
|
traces.push({ path: META_FILE, tracks: readMetafile(at5(META_FILE)) });
|
|
@@ -7175,7 +7244,7 @@ function collectTraces(projectDir) {
|
|
|
7175
7244
|
}
|
|
7176
7245
|
function readMetafile(path) {
|
|
7177
7246
|
const seen = /* @__PURE__ */ new Set();
|
|
7178
|
-
for (const word of
|
|
7247
|
+
for (const word of readFileSync21(path, "utf8").split(/\s+/)) {
|
|
7179
7248
|
if (isTrack(word)) seen.add(word);
|
|
7180
7249
|
}
|
|
7181
7250
|
return [...seen].sort();
|
|
@@ -7220,13 +7289,13 @@ import {
|
|
|
7220
7289
|
copyFileSync,
|
|
7221
7290
|
existsSync as existsSync21,
|
|
7222
7291
|
readdirSync as readdirSync4,
|
|
7223
|
-
readFileSync as
|
|
7292
|
+
readFileSync as readFileSync22,
|
|
7224
7293
|
rmdirSync as rmdirSync2,
|
|
7225
7294
|
statSync as statSync4,
|
|
7226
7295
|
unlinkSync as unlinkSync2,
|
|
7227
7296
|
writeFileSync as writeFileSync7
|
|
7228
7297
|
} from "fs";
|
|
7229
|
-
import { basename as basename3, dirname as dirname7, join as join30 } from "path";
|
|
7298
|
+
import { basename as basename3, dirname as dirname7, join as join30, relative as relative5, sep as sep6 } from "path";
|
|
7230
7299
|
var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
|
|
7231
7300
|
function buildUpdateSpec(projectDir, tracks, only) {
|
|
7232
7301
|
const log = readInstallLog(projectDir);
|
|
@@ -7288,11 +7357,12 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7288
7357
|
const logAtStart = readInstallLog(projectDir);
|
|
7289
7358
|
const claudeManaged = logAtStart === null || installedClis(logAtStart).includes("claude");
|
|
7290
7359
|
report.claudeUnrecorded = !claudeManaged && logAtStart !== null && existsSync21(claudeDir) ? recordClaudeCommand(logAtStart, installedTracks(projectDir)) : null;
|
|
7291
|
-
const harness = logAtStart === null ? { displaced: [], restored: [], foreignOwned: [], outside: [] } : updateHarnessFiles({ projectDir, templatesDir, log: logAtStart, wants, outside, report });
|
|
7360
|
+
const harness = logAtStart === null ? { displaced: [], restored: [], foreignOwned: [], outside: [], judged: [] } : updateHarnessFiles({ projectDir, templatesDir, log: logAtStart, wants, outside, report });
|
|
7292
7361
|
const claudeDisplaced = harness.displaced;
|
|
7362
|
+
const judged = [...harness.judged];
|
|
7293
7363
|
const prunedRestored = harness.restored;
|
|
7294
7364
|
if (existsSync21(claudeDir) && claudeManaged) {
|
|
7295
|
-
if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report, outside);
|
|
7365
|
+
if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report, outside, judged);
|
|
7296
7366
|
}
|
|
7297
7367
|
const harnessMcp = renderHarnessMcp(harnessRoot, {
|
|
7298
7368
|
spec: { tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS },
|
|
@@ -7326,9 +7396,16 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7326
7396
|
if (wants("hooks")) report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
7327
7397
|
report.retiredAgents = staleAgentFiles(claudeDir);
|
|
7328
7398
|
report.demotedAgents = demotedAgentFiles(claudeDir, installedTracks(projectDir));
|
|
7329
|
-
const external = wants("external") ? refreshExternalCli(
|
|
7399
|
+
const external = wants("external") ? refreshExternalCli(
|
|
7400
|
+
projectDir,
|
|
7401
|
+
harnessRoot,
|
|
7402
|
+
legacyDroppedKeys(logAtStart),
|
|
7403
|
+
harnessMcp,
|
|
7404
|
+
report.skillsSkippedLinks.filter((id) => linksToProjectSharedSkill(projectDir, id))
|
|
7405
|
+
) : {
|
|
7330
7406
|
externalUpdated: 0,
|
|
7331
7407
|
externalBackedUp: [],
|
|
7408
|
+
externalJudged: [],
|
|
7332
7409
|
displaced: [],
|
|
7333
7410
|
externalForeignOwned: [],
|
|
7334
7411
|
externalOutside: [],
|
|
@@ -7439,10 +7516,17 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7439
7516
|
const displacedSame = displacedAll.filter((f) => f.notes.length === 0).map((f) => f.path);
|
|
7440
7517
|
if (displacedMoved.length > 0) report.displaced = displacedMoved;
|
|
7441
7518
|
if (displacedSame.length > 0) report.displacedSame = displacedSame;
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7519
|
+
const deadMcp = claudeManaged ? deadMcpLeft(projectDir) : [];
|
|
7520
|
+
if (deadMcp.length > 0) report.deadMcp = deadMcp;
|
|
7521
|
+
judged.push(...external.externalJudged, ...skillRefresh.judged ?? []);
|
|
7522
|
+
if (judged.length > 0) report.judged = judged;
|
|
7523
|
+
const unmeasured = new Set(
|
|
7524
|
+
judged.filter((j4) => j4.recorded === "no-sha" && j4.backup !== void 0).map((j4) => j4.path)
|
|
7525
|
+
);
|
|
7526
|
+
report.backups = collectRunBackups(projectDir, startedAt, backupsAtStart).map(
|
|
7527
|
+
(b2) => existsSync21(join30(projectDir, b2.path)) ? b2 : { ...b2, removed: true }
|
|
7528
|
+
);
|
|
7529
|
+
const noChecksum = report.backups.map((b2) => b2.path).filter((p2) => unmeasured.has(p2));
|
|
7446
7530
|
if (noChecksum.length > 0) report.noChecksum = noChecksum;
|
|
7447
7531
|
writeBackupList(projectDir, report.backups, new Set(noChecksum));
|
|
7448
7532
|
return report;
|
|
@@ -7520,19 +7604,16 @@ function reclaimOutOfTrackRecorded(projectDir, outside) {
|
|
|
7520
7604
|
}
|
|
7521
7605
|
return { reclaim: result, displaced };
|
|
7522
7606
|
}
|
|
7523
|
-
function
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
const anchor = log.templates.rootClaudeMd;
|
|
7534
|
-
if (anchor !== void 0) paths.add(anchor.path);
|
|
7535
|
-
return (path) => paths.has(path);
|
|
7607
|
+
function deadMcpLeft(projectDir) {
|
|
7608
|
+
let servers;
|
|
7609
|
+
try {
|
|
7610
|
+
servers = JSON.parse(readFileSync22(join30(projectDir, ".mcp.json"), "utf8")).mcpServers;
|
|
7611
|
+
} catch {
|
|
7612
|
+
return [];
|
|
7613
|
+
}
|
|
7614
|
+
return PRIOR_MCP_SERVERS.flatMap(
|
|
7615
|
+
(p2) => p2.dead !== void 0 && servers?.[p2.name] !== void 0 && jsonSha(servers[p2.name]) === jsonSha(toClaudeServer(p2.value)) ? [{ name: p2.name, note: p2.dead }] : []
|
|
7616
|
+
);
|
|
7536
7617
|
}
|
|
7537
7618
|
function retiredFeaturesPart(projectDir, f) {
|
|
7538
7619
|
if (f.path !== ".codex/config.toml") return {};
|
|
@@ -7541,7 +7622,7 @@ function retiredFeaturesPart(projectDir, f) {
|
|
|
7541
7622
|
}
|
|
7542
7623
|
function legacyHookOnDisk(projectDir, rel) {
|
|
7543
7624
|
try {
|
|
7544
|
-
return hasLegacyHarnessHook(
|
|
7625
|
+
return hasLegacyHarnessHook(readFileSync22(join30(projectDir, rel), "utf8"));
|
|
7545
7626
|
} catch {
|
|
7546
7627
|
return false;
|
|
7547
7628
|
}
|
|
@@ -7573,7 +7654,7 @@ function writeUpdateSharedFiles(args) {
|
|
|
7573
7654
|
const manifest = buildManifest(spec);
|
|
7574
7655
|
writeSettingsShared(
|
|
7575
7656
|
writer,
|
|
7576
|
-
|
|
7657
|
+
readFileSync22(settingsSource, "utf8"),
|
|
7577
7658
|
projectDir,
|
|
7578
7659
|
log,
|
|
7579
7660
|
(script) => {
|
|
@@ -7726,6 +7807,7 @@ function updateHarnessTargets(projectDir, templatesDir, log, wants, report, fore
|
|
|
7726
7807
|
}
|
|
7727
7808
|
function updateHarnessFiles(args) {
|
|
7728
7809
|
const { projectDir, templatesDir, log, wants, outside, report } = args;
|
|
7810
|
+
const judged = [];
|
|
7729
7811
|
const now = /* @__PURE__ */ new Date();
|
|
7730
7812
|
const claude = installedClis(log).includes("claude");
|
|
7731
7813
|
const claims = retiredClaims(log, projectDir, log.policyFiles ?? []);
|
|
@@ -7746,7 +7828,7 @@ function updateHarnessFiles(args) {
|
|
|
7746
7828
|
if (!claude || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
|
|
7747
7829
|
const label = `.claude/${dir}`;
|
|
7748
7830
|
report.updated[label] = 0;
|
|
7749
|
-
const ctx = { prefix: dir, outside, recordOf };
|
|
7831
|
+
const ctx = { prefix: dir, outside, recordOf, projectDir, judged };
|
|
7750
7832
|
report.pruned[label] = pruneOrphans(
|
|
7751
7833
|
join30(projectDir, label),
|
|
7752
7834
|
join30(templatesDir, dir),
|
|
@@ -7763,7 +7845,8 @@ function updateHarnessFiles(args) {
|
|
|
7763
7845
|
targets.refreshedSkills,
|
|
7764
7846
|
outside,
|
|
7765
7847
|
recordOf,
|
|
7766
|
-
now
|
|
7848
|
+
now,
|
|
7849
|
+
judged
|
|
7767
7850
|
);
|
|
7768
7851
|
report.skillsPruned = skillPrune.pruned;
|
|
7769
7852
|
report.skillsBackedUp.push(...skillPrune.backedUp);
|
|
@@ -7772,6 +7855,11 @@ function updateHarnessFiles(args) {
|
|
|
7772
7855
|
write: true
|
|
7773
7856
|
});
|
|
7774
7857
|
const ledger = writer.ledger();
|
|
7858
|
+
judged.unshift(
|
|
7859
|
+
...ledger.writes.flatMap(
|
|
7860
|
+
({ path, verdict, recorded: recorded2, line, backup, backupAbs }) => backup === void 0 || backupAbs === void 0 ? [] : [{ path, verdict, recorded: recorded2, line, backup, backupAbs }]
|
|
7861
|
+
)
|
|
7862
|
+
);
|
|
7775
7863
|
recordHarnessWrites(projectDir, ledger, {
|
|
7776
7864
|
claims,
|
|
7777
7865
|
prunedPolicy,
|
|
@@ -7802,10 +7890,25 @@ function updateHarnessFiles(args) {
|
|
|
7802
7890
|
displaced: ledger.rootFiles.filter((f) => f.change === "displaced"),
|
|
7803
7891
|
restored,
|
|
7804
7892
|
foreignOwned,
|
|
7805
|
-
outside: ledger.outside
|
|
7893
|
+
outside: ledger.outside,
|
|
7894
|
+
judged
|
|
7895
|
+
};
|
|
7896
|
+
}
|
|
7897
|
+
function removedRow(projectDir, path, why, judged, backupAbs) {
|
|
7898
|
+
const { line, recorded: recorded2 } = judged;
|
|
7899
|
+
if (backupAbs === null) return { path, verdict: "remove", recorded: recorded2, line: `${why} \u2014 ${line}` };
|
|
7900
|
+
const backup = relative5(projectDir, backupAbs).split(sep6).join("/");
|
|
7901
|
+
return {
|
|
7902
|
+
path,
|
|
7903
|
+
verdict: "backup+remove",
|
|
7904
|
+
recorded: recorded2,
|
|
7905
|
+
line: `${why} \u2014 ${withBackup(line, backup)}`,
|
|
7906
|
+
backup,
|
|
7907
|
+
backupAbs
|
|
7806
7908
|
};
|
|
7807
7909
|
}
|
|
7808
|
-
|
|
7910
|
+
var NOT_RECORDED_REMOVED = "not on the install record \u2014 saved as <file>.backup-<time>, removed from live";
|
|
7911
|
+
function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now, judged) {
|
|
7809
7912
|
const pruned = [];
|
|
7810
7913
|
const backedUp = [];
|
|
7811
7914
|
for (const id of ids) {
|
|
@@ -7816,11 +7919,18 @@ function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now)
|
|
|
7816
7919
|
if (foreignOwnedTarget(projectDir, `.claude/skills/${id}/${rel}`) !== null) continue;
|
|
7817
7920
|
const targetFile = join30(targetSkill, rel);
|
|
7818
7921
|
if (outside.skip(targetFile)) continue;
|
|
7819
|
-
const
|
|
7820
|
-
|
|
7821
|
-
|
|
7922
|
+
const path = `.claude/skills/${id}/${rel}`;
|
|
7923
|
+
const rec = recordOf(path);
|
|
7924
|
+
const disk = rec.state === "sha" ? readFileSync22(targetFile, "utf8") : "";
|
|
7925
|
+
const line = rec.state === "none" ? NOT_RECORDED_REMOVED : judge({ op: "remove", kind: "harness", rec, disk, next: null }).line;
|
|
7926
|
+
let backupAbs = null;
|
|
7927
|
+
if (rec.state !== "sha" || hashContent(disk) !== rec.sha256) {
|
|
7928
|
+
backupAbs = backupFile(targetFile, now);
|
|
7822
7929
|
backedUp.push(`${id}/${rel}`);
|
|
7823
7930
|
}
|
|
7931
|
+
judged.push(
|
|
7932
|
+
removedRow(projectDir, path, "not in bundle", { line, recorded: rec.state }, backupAbs)
|
|
7933
|
+
);
|
|
7824
7934
|
unlinkSync2(targetFile);
|
|
7825
7935
|
pruned.push(`${id}/${rel}`);
|
|
7826
7936
|
for (let d2 = dirname7(targetFile); d2 !== targetSkill && readdirSync4(d2).length === 0; d2 = dirname7(d2))
|
|
@@ -7895,7 +8005,7 @@ function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new S
|
|
|
7895
8005
|
`${JSON.stringify(
|
|
7896
8006
|
{
|
|
7897
8007
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7898
|
-
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.",
|
|
8008
|
+
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.",
|
|
7899
8009
|
backups: backups.map((b2) => noChecksum.has(b2.path) ? { ...b2, noChecksum: true } : b2)
|
|
7900
8010
|
},
|
|
7901
8011
|
null,
|
|
@@ -7914,7 +8024,7 @@ function trackOnlyFileAssets(tracks) {
|
|
|
7914
8024
|
(e2) => e2.type === "file" && e2.applies(optedOut) && e2.applies(optedIn)
|
|
7915
8025
|
);
|
|
7916
8026
|
}
|
|
7917
|
-
function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @__PURE__ */ new Date()) {
|
|
8027
|
+
function syncHarnessAnchor(projectDir, templatesDir, report, outside, judged, now = /* @__PURE__ */ new Date()) {
|
|
7918
8028
|
if (existsSync21(join30(projectDir, LEGACY_ANCHOR_FILE2))) {
|
|
7919
8029
|
report.legacyAnchor = LEGACY_ANCHOR_FILE2;
|
|
7920
8030
|
}
|
|
@@ -7924,13 +8034,25 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
|
|
|
7924
8034
|
if (outside.skip(anchor)) return;
|
|
7925
8035
|
const existed = existsSync21(anchor);
|
|
7926
8036
|
if (existed) {
|
|
7927
|
-
const current =
|
|
7928
|
-
if (current !==
|
|
8037
|
+
const current = readFileSync22(anchor, "utf8");
|
|
8038
|
+
if (current !== readFileSync22(templateMd, "utf8")) {
|
|
7929
8039
|
const recorded2 = readInstallLog(projectDir)?.templates.rootClaudeMd;
|
|
7930
8040
|
const owned = recorded2?.path === HARNESS_ANCHOR_FILE && hashContent(current) === recorded2.sha256;
|
|
7931
8041
|
if (!owned) {
|
|
7932
|
-
backupFile(anchor, now);
|
|
8042
|
+
const backup = backupFile(anchor, now);
|
|
7933
8043
|
report.anchorBackedUp = true;
|
|
8044
|
+
const rel = relative5(projectDir, backup).split(sep6).join("/");
|
|
8045
|
+
const rec = recorded2?.path === HARNESS_ANCHOR_FILE ? { state: "sha", sha256: recorded2.sha256 } : { state: "no-sha" };
|
|
8046
|
+
const next = readFileSync22(templateMd, "utf8");
|
|
8047
|
+
const { line } = judge({ op: "write", kind: "harness", rec, disk: current, next });
|
|
8048
|
+
judged.push({
|
|
8049
|
+
path: HARNESS_ANCHOR_FILE,
|
|
8050
|
+
verdict: "backup+overwrite",
|
|
8051
|
+
recorded: rec.state,
|
|
8052
|
+
line: withBackup(line, rel),
|
|
8053
|
+
backup: rel,
|
|
8054
|
+
backupAbs: backup
|
|
8055
|
+
});
|
|
7934
8056
|
}
|
|
7935
8057
|
copyFileSync(templateMd, anchor);
|
|
7936
8058
|
report.claudeMdUpdated = true;
|
|
@@ -7948,7 +8070,7 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
|
|
|
7948
8070
|
function upsertRootImport(projectDir, outside) {
|
|
7949
8071
|
const target = join30(projectDir, "CLAUDE.md");
|
|
7950
8072
|
if (outside.skip(target)) return false;
|
|
7951
|
-
const existing = existsSync21(target) ?
|
|
8073
|
+
const existing = existsSync21(target) ? readFileSync22(target, "utf8") : null;
|
|
7952
8074
|
const next = upsertHarnessImport(existing, {
|
|
7953
8075
|
projectName: basename3(projectDir),
|
|
7954
8076
|
tracks: installedTracks(projectDir),
|
|
@@ -7976,7 +8098,7 @@ function recordAnchorBaseline(projectDir, anchor) {
|
|
|
7976
8098
|
...log.templates,
|
|
7977
8099
|
rootClaudeMd: {
|
|
7978
8100
|
path: HARNESS_ANCHOR_FILE,
|
|
7979
|
-
sha256: hashContent(
|
|
8101
|
+
sha256: hashContent(readFileSync22(anchor, "utf8"))
|
|
7980
8102
|
}
|
|
7981
8103
|
}
|
|
7982
8104
|
};
|
|
@@ -8094,7 +8216,7 @@ function changedRemoteMcp(writes) {
|
|
|
8094
8216
|
const changed = /* @__PURE__ */ new Set([...mcp.added, ...mcp.replaced ?? []]);
|
|
8095
8217
|
return (mcp.remote ?? []).filter((r) => changed.has(r.name));
|
|
8096
8218
|
}
|
|
8097
|
-
function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
8219
|
+
function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp, linkedIds) {
|
|
8098
8220
|
const log = readInstallLog(projectDir);
|
|
8099
8221
|
const presentBefore = sharedSkillIdsOnDisk(projectDir);
|
|
8100
8222
|
const rulesBefore = /* @__PURE__ */ new Set();
|
|
@@ -8126,11 +8248,23 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
8126
8248
|
// 앞 단계(하네스 파일 쓰기)가 헬퍼만 든 `externalFiles` 를 적어도 같은 답이다 — 헬퍼는 외부 자리로 세지 않는다(B-2)
|
|
8127
8249
|
owned: recordedOwner(log)
|
|
8128
8250
|
});
|
|
8251
|
+
const linked = refreshLinkedSkillBodies({
|
|
8252
|
+
harnessRoot,
|
|
8253
|
+
projectDir,
|
|
8254
|
+
ids: linkedIds.filter(
|
|
8255
|
+
(id) => !result.externalFiles.some((f) => f.path === `${SHARED_SKILLS_DIR}/${id}/SKILL.md`)
|
|
8256
|
+
),
|
|
8257
|
+
baseline: new Map(
|
|
8258
|
+
mergeExternalFiles(log?.externalFiles, result.externalFiles).map((f) => [f.path, f.sha256])
|
|
8259
|
+
)
|
|
8260
|
+
});
|
|
8261
|
+
const written = [...result.externalFiles, ...linked.files];
|
|
8262
|
+
const displaced = [...result.displaced ?? [], ...linked.displaced];
|
|
8129
8263
|
const anchorPath = result.anchorRetired?.action === "removed" ? result.anchorRetired.path : null;
|
|
8130
8264
|
const anchorDisplaced = log === null || anchorPath === null ? [] : restoreDisplaced(log, projectDir, { has: (p2) => p2 === anchorPath, write: true });
|
|
8131
8265
|
if (log) {
|
|
8132
8266
|
const retiredPath = result.anchorRetired?.forget === true ? result.anchorRetired.path : null;
|
|
8133
|
-
const merged = mergeExternalFiles(log.externalFiles,
|
|
8267
|
+
const merged = mergeExternalFiles(log.externalFiles, written).filter(
|
|
8134
8268
|
(f) => f.path !== retiredPath
|
|
8135
8269
|
);
|
|
8136
8270
|
const next = retiredPath === null ? { ...log } : { ...log, spec: { ...log.spec, clis: [...installedClis(log)] } };
|
|
@@ -8144,7 +8278,7 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
8144
8278
|
if (portions.length > 0) next.portions = portions;
|
|
8145
8279
|
else delete next.portions;
|
|
8146
8280
|
const created = result.sharedFiles.filter((f) => f.createdAsRoot === true).map((f) => ({ path: f.path, change: "created", notes: ["\uD558\uB124\uC2A4 \uBAAB\uB9CC(\uBE14\uB85D)"] }));
|
|
8147
|
-
const roots = [...created, ...withoutLostBackups(log.rootFiles,
|
|
8281
|
+
const roots = [...created, ...withoutLostBackups(log.rootFiles, displaced)];
|
|
8148
8282
|
if (roots.length > 0) next.rootFiles = mergeRootFiles(log.rootFiles, roots);
|
|
8149
8283
|
if (anchorDisplaced.some((d2) => d2.forget))
|
|
8150
8284
|
next.rootFiles = withoutRestored(next.rootFiles, anchorDisplaced);
|
|
@@ -8203,11 +8337,12 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
8203
8337
|
const dropped = new Set(droppedKeys);
|
|
8204
8338
|
return {
|
|
8205
8339
|
externalUpdated: result.externalUpdated,
|
|
8206
|
-
externalBackedUp: result.externalBackedUp,
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8340
|
+
externalBackedUp: [...result.externalBackedUp, ...linked.backedUp],
|
|
8341
|
+
externalJudged: [...result.externalJudged, ...linked.judged],
|
|
8342
|
+
displaced,
|
|
8343
|
+
externalForeignOwned: [...result.externalForeignOwned, ...linked.foreignOwned],
|
|
8344
|
+
externalOutside: [...result.externalOutside, ...linked.outside],
|
|
8345
|
+
written: written.map((f) => f.path),
|
|
8211
8346
|
skillsInstalled,
|
|
8212
8347
|
skillsRestored,
|
|
8213
8348
|
rulesRestored,
|
|
@@ -8262,14 +8397,24 @@ function pruneOrphans(target, source, ext, ctx, now) {
|
|
|
8262
8397
|
for (const file of readdirSync4(target)) {
|
|
8263
8398
|
if (!file.endsWith(ext) || existsSync21(join30(source, file))) continue;
|
|
8264
8399
|
const targetFile = join30(target, file);
|
|
8265
|
-
const
|
|
8400
|
+
const path = `.claude/${ctx.prefix}/${file}`;
|
|
8401
|
+
const rec = ctx.recordOf(path);
|
|
8266
8402
|
try {
|
|
8267
|
-
const disk =
|
|
8268
|
-
const { verdict } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
|
|
8403
|
+
const disk = readFileSync22(targetFile, "utf8");
|
|
8404
|
+
const { verdict, line } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
|
|
8269
8405
|
if (verdict === "leave" || ctx.outside.skip(targetFile)) continue;
|
|
8270
|
-
|
|
8406
|
+
const backupAbs = verdict === "backup+remove" ? backupFile(targetFile, now) : null;
|
|
8271
8407
|
unlinkSync2(targetFile);
|
|
8272
8408
|
removed.push(file);
|
|
8409
|
+
ctx.judged.push(
|
|
8410
|
+
removedRow(
|
|
8411
|
+
ctx.projectDir,
|
|
8412
|
+
path,
|
|
8413
|
+
"retired from the harness",
|
|
8414
|
+
{ line, recorded: rec.state },
|
|
8415
|
+
backupAbs
|
|
8416
|
+
)
|
|
8417
|
+
);
|
|
8273
8418
|
} catch {
|
|
8274
8419
|
}
|
|
8275
8420
|
}
|
|
@@ -8330,10 +8475,10 @@ function runInstall(ctx) {
|
|
|
8330
8475
|
const settingsPath = join31(projectDir, ".claude", "settings.json");
|
|
8331
8476
|
if (existsSync22(settingsPath)) {
|
|
8332
8477
|
try {
|
|
8333
|
-
JSON.parse(
|
|
8478
|
+
JSON.parse(readFileSync23(settingsPath, "utf8"));
|
|
8334
8479
|
} catch {
|
|
8335
8480
|
throw new Error(
|
|
8336
|
-
`${
|
|
8481
|
+
`${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`
|
|
8337
8482
|
);
|
|
8338
8483
|
}
|
|
8339
8484
|
}
|
|
@@ -8402,6 +8547,7 @@ function runInstallStages(ctx, args) {
|
|
|
8402
8547
|
externalBackups,
|
|
8403
8548
|
externalUpdated: _externalUpdated,
|
|
8404
8549
|
externalBackedUp: _externalBackedUp,
|
|
8550
|
+
externalJudged: _externalJudged,
|
|
8405
8551
|
displaced: cliDisplaced = [],
|
|
8406
8552
|
externalForeignOwned,
|
|
8407
8553
|
externalOutside,
|
|
@@ -8649,7 +8795,7 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
|
|
|
8649
8795
|
if (path === null || done.has(path) || !existsSync22(join31(projectDir, path))) return [];
|
|
8650
8796
|
const name = adapterFor(path);
|
|
8651
8797
|
const keys = [...excludedKeys(path, [id])];
|
|
8652
|
-
const present2 = name === null ? null : ADAPTERS[name].read(
|
|
8798
|
+
const present2 = name === null ? null : ADAPTERS[name].read(readFileSync23(join31(projectDir, path), "utf8"), keys, projectDir);
|
|
8653
8799
|
return present2 !== null && present2.size === 0 ? [] : [{ id, path }];
|
|
8654
8800
|
});
|
|
8655
8801
|
}
|
|
@@ -8810,7 +8956,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
|
|
|
8810
8956
|
function writeSettingsPortion(writer, source, projectDir, previousLog, hookInstalled) {
|
|
8811
8957
|
return writeSettingsShared(
|
|
8812
8958
|
writer,
|
|
8813
|
-
|
|
8959
|
+
readFileSync23(source, "utf8"),
|
|
8814
8960
|
projectDir,
|
|
8815
8961
|
previousLog,
|
|
8816
8962
|
hookInstalled
|
|
@@ -8954,7 +9100,7 @@ function recordInterruptedInstall(ctx, error, run) {
|
|
|
8954
9100
|
const codexTrust = registeredTrust(journal.codexOptIn, ctx.projectDir);
|
|
8955
9101
|
const written = writtenSoFar(ledger, cliFiles, rootFiles, stage.rootImportWritten);
|
|
8956
9102
|
if (codexTrust) written.push(`${codexTrust.configPath} (Codex trust entry for this folder)`);
|
|
8957
|
-
const projectRel = (abs) =>
|
|
9103
|
+
const projectRel = (abs) => relative6(ctx.projectDir, abs).split(sep7).join("/");
|
|
8958
9104
|
const writtenSet = new Set(written);
|
|
8959
9105
|
const backups = [...own.backups, ...journal.backups].map((abs) => {
|
|
8960
9106
|
const original = projectRel(backupOriginal(abs) ?? abs);
|
|
@@ -9096,7 +9242,7 @@ function accumulateCategory(cats, entry) {
|
|
|
9096
9242
|
function writeRootClaudeMd(projectDir, tracks, continuousSkills, harnessRoot, writer) {
|
|
9097
9243
|
const target = join31(projectDir, "CLAUDE.md");
|
|
9098
9244
|
if (writer.skipOutside(target)) return null;
|
|
9099
|
-
const existing = existsSync22(target) ?
|
|
9245
|
+
const existing = existsSync22(target) ? readFileSync23(target, "utf-8") : null;
|
|
9100
9246
|
const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
|
|
9101
9247
|
const content = upsertHarnessImport(existing, {
|
|
9102
9248
|
projectName: basename4(projectDir),
|
|
@@ -9130,8 +9276,8 @@ function listHookFiles(hookDir) {
|
|
|
9130
9276
|
// src/commands/install-render.ts
|
|
9131
9277
|
init_esm_shims();
|
|
9132
9278
|
import { homedir as homedir3 } from "os";
|
|
9133
|
-
import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as
|
|
9134
|
-
import { join as join33, relative as
|
|
9279
|
+
import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync25 } from "fs";
|
|
9280
|
+
import { join as join33, relative as relative7 } from "path";
|
|
9135
9281
|
|
|
9136
9282
|
// src/preset-recommend.ts
|
|
9137
9283
|
init_esm_shims();
|
|
@@ -9167,7 +9313,7 @@ function groupAssetsByCategory(assetIds) {
|
|
|
9167
9313
|
|
|
9168
9314
|
// src/resident-entries.ts
|
|
9169
9315
|
init_esm_shims();
|
|
9170
|
-
import { existsSync as existsSync23, readFileSync as
|
|
9316
|
+
import { existsSync as existsSync23, readFileSync as readFileSync24 } from "fs";
|
|
9171
9317
|
import { basename as basename5, join as join32 } from "path";
|
|
9172
9318
|
function residentEntries(spec) {
|
|
9173
9319
|
const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync23(join32(spec.projectDir, d2, id)));
|
|
@@ -9208,7 +9354,7 @@ function plannedClaudeContext(spec) {
|
|
|
9208
9354
|
if (!spec.cli.includes("claude")) return void 0;
|
|
9209
9355
|
const root = resolveBundleRoot();
|
|
9210
9356
|
const rootClaude = join32(spec.projectDir, "CLAUDE.md");
|
|
9211
|
-
const existing = existsSync23(rootClaude) ?
|
|
9357
|
+
const existing = existsSync23(rootClaude) ? readFileSync24(rootClaude, "utf-8") : null;
|
|
9212
9358
|
const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
|
|
9213
9359
|
if (existing === null && seeded === null) return void 0;
|
|
9214
9360
|
const manifestSpec = buildManifestSpec(spec);
|
|
@@ -9359,9 +9505,11 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
9359
9505
|
});
|
|
9360
9506
|
const backups = report.updateMode?.backups ?? [];
|
|
9361
9507
|
if (backups.length > 0) {
|
|
9362
|
-
const
|
|
9363
|
-
const
|
|
9364
|
-
const
|
|
9508
|
+
const replaced = backups.filter((b2) => b2.removed !== true);
|
|
9509
|
+
const removedBefore = backups.length - replaced.length;
|
|
9510
|
+
const noChecksum = new Set(report.updateMode?.noChecksum ?? []);
|
|
9511
|
+
const unmeasured = replaced.filter((b2) => noChecksum.has(b2.path)).length;
|
|
9512
|
+
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`;
|
|
9365
9513
|
const how = removedBefore === 0 ? "saved before replacing" : removedBefore === backups.length ? "saved before removing" : `saved before replacing (${removedBefore} before removing)`;
|
|
9366
9514
|
log(
|
|
9367
9515
|
infoRow(
|
|
@@ -9369,12 +9517,13 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
9369
9517
|
`${backups.length} file(s) ${how}, as *.backup-<time>${unmeasuredPart} \xB7 list: .uzys-agent-harness/update-backups.json`
|
|
9370
9518
|
)
|
|
9371
9519
|
);
|
|
9372
|
-
const
|
|
9373
|
-
|
|
9520
|
+
const displaced = new Set(report.updateMode?.displaced ?? []);
|
|
9521
|
+
const carry = replaced.filter((b2) => !displaced.has(b2.path)).length;
|
|
9522
|
+
if (carry > 0)
|
|
9374
9523
|
log(
|
|
9375
9524
|
infoRow(
|
|
9376
9525
|
"NEXT",
|
|
9377
|
-
unmeasured ===
|
|
9526
|
+
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)'
|
|
9378
9527
|
)
|
|
9379
9528
|
);
|
|
9380
9529
|
}
|
|
@@ -9671,7 +9820,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
9671
9820
|
const home = process.env.CODEX_HOME ?? join33(homedir3(), ".codex");
|
|
9672
9821
|
const configPath = join33(home, "config.toml");
|
|
9673
9822
|
if (!existsSync24(configPath)) return false;
|
|
9674
|
-
return hasTrustEntry(
|
|
9823
|
+
return hasTrustEntry(readFileSync25(configPath, "utf8"), spec.projectDir);
|
|
9675
9824
|
} catch {
|
|
9676
9825
|
return false;
|
|
9677
9826
|
}
|
|
@@ -9712,7 +9861,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
9712
9861
|
}
|
|
9713
9862
|
function hasFillPrompt(path) {
|
|
9714
9863
|
try {
|
|
9715
|
-
return
|
|
9864
|
+
return readFileSync25(path, "utf8").includes("<!-- FILL:");
|
|
9716
9865
|
} catch {
|
|
9717
9866
|
return false;
|
|
9718
9867
|
}
|
|
@@ -9766,25 +9915,8 @@ function linkedSkillRows(log, updated, notOurs) {
|
|
|
9766
9915
|
);
|
|
9767
9916
|
}
|
|
9768
9917
|
}
|
|
9769
|
-
function hadFileRow(log, area, paths) {
|
|
9770
|
-
if (paths.length === 0) return;
|
|
9771
|
-
log(
|
|
9772
|
-
assetRow(
|
|
9773
|
-
"skip",
|
|
9774
|
-
`${area} had files with these names`,
|
|
9775
|
-
`${paths.length} backed up as *.backup-<time> \u2014 yours, put back on uninstall \xB7 ${paths.join(", ")}`
|
|
9776
|
-
)
|
|
9777
|
-
);
|
|
9778
|
-
}
|
|
9779
9918
|
function renderPhase1Rows(log, baseline, verbose = false) {
|
|
9780
9919
|
if (baseline.updateMode) {
|
|
9781
|
-
const noChecksum = new Set(baseline.updateMode.noChecksum ?? []);
|
|
9782
|
-
const displaced = baseline.updateMode.displaced ?? [];
|
|
9783
|
-
const displacedSkills = displaced.filter((p2) => p2.startsWith(".claude/skills/"));
|
|
9784
|
-
const displacedPolicy = displaced.filter(
|
|
9785
|
-
(p2) => p2.startsWith(".claude/") && !p2.startsWith(".claude/skills/")
|
|
9786
|
-
);
|
|
9787
|
-
const displacedExternal = displaced.filter((p2) => !p2.startsWith(".claude/"));
|
|
9788
9920
|
for (const [dir, count] of Object.entries(baseline.updateMode.updated)) {
|
|
9789
9921
|
if (count > 0) log(assetRow("success", dir, `${count} files updated`));
|
|
9790
9922
|
}
|
|
@@ -9841,6 +9973,8 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9841
9973
|
const row = sharedFileRow(f);
|
|
9842
9974
|
if (row !== null) log(row);
|
|
9843
9975
|
}
|
|
9976
|
+
for (const d2 of baseline.updateMode.deadMcp ?? [])
|
|
9977
|
+
log(assetRow("skip", ".mcp.json", `${d2.name} \u2014 ${d2.note}`));
|
|
9844
9978
|
for (const f of baseline.updateMode.sharedLeft ?? []) {
|
|
9845
9979
|
log(sharedLeftRow(f));
|
|
9846
9980
|
if (f.retiredFeatures !== void 0) log(retiredFeaturesRow(f.path, f.retiredFeatures));
|
|
@@ -9854,11 +9988,7 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9854
9988
|
if (legacy2 !== null) log(legacy2);
|
|
9855
9989
|
for (const row of legacyReleasedCatalogRows(baseline.updateMode.legacyReleasedCatalog ?? []))
|
|
9856
9990
|
log(row);
|
|
9857
|
-
for (const
|
|
9858
|
-
if (removed.length > 0) {
|
|
9859
|
-
log(assetRow("skip", `${dir} orphan prune`, `${removed.length} removed`));
|
|
9860
|
-
}
|
|
9861
|
-
}
|
|
9991
|
+
for (const j4 of baseline.updateMode.judged ?? []) log(judgedRow(j4));
|
|
9862
9992
|
if (baseline.updateMode.skippedGroups.length > 0) {
|
|
9863
9993
|
log(
|
|
9864
9994
|
assetRow(
|
|
@@ -9871,15 +10001,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9871
10001
|
if (baseline.updateMode.claudeMdUpdated) {
|
|
9872
10002
|
log(assetRow("success", HARNESS_ANCHOR_FILE, "refreshed from template"));
|
|
9873
10003
|
}
|
|
9874
|
-
if (baseline.updateMode.anchorBackedUp && !noChecksum.has(HARNESS_ANCHOR_FILE)) {
|
|
9875
|
-
log(
|
|
9876
|
-
assetRow(
|
|
9877
|
-
"skip",
|
|
9878
|
-
`${HARNESS_ANCHOR_FILE} edited`,
|
|
9879
|
-
"your edits backed up as *.backup-<time> \u2014 latest template is now active"
|
|
9880
|
-
)
|
|
9881
|
-
);
|
|
9882
|
-
}
|
|
9883
10004
|
if (baseline.updateMode.anchorCreated) {
|
|
9884
10005
|
log(assetRow("success", HARNESS_ANCHOR_FILE, "created from template (anchor migration)"));
|
|
9885
10006
|
}
|
|
@@ -9907,28 +10028,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9907
10028
|
if (baseline.updateMode.legacyAnchor) {
|
|
9908
10029
|
log(assetRow("skip", baseline.updateMode.legacyAnchor, LEGACY_ANCHOR_NOTE));
|
|
9909
10030
|
}
|
|
9910
|
-
const skillsEdited = baseline.updateMode.skillsBackedUp.filter(
|
|
9911
|
-
(p2) => !noChecksum.has(`.claude/skills/${p2}`)
|
|
9912
|
-
);
|
|
9913
|
-
if (skillsEdited.length > 0) {
|
|
9914
|
-
log(
|
|
9915
|
-
assetRow(
|
|
9916
|
-
"skip",
|
|
9917
|
-
".claude/skills edited files",
|
|
9918
|
-
`${skillsEdited.length} backed up as *.backup-<time>`
|
|
9919
|
-
)
|
|
9920
|
-
);
|
|
9921
|
-
}
|
|
9922
|
-
hadFileRow(log, ".claude/skills", displacedSkills);
|
|
9923
|
-
if (baseline.updateMode.skillsPruned.length > 0) {
|
|
9924
|
-
log(
|
|
9925
|
-
assetRow(
|
|
9926
|
-
"success",
|
|
9927
|
-
".claude/skills not in bundle",
|
|
9928
|
-
`${baseline.updateMode.skillsPruned.length} files deleted \xB7 ${baseline.updateMode.skillsPruned.join(", ")}`
|
|
9929
|
-
)
|
|
9930
|
-
);
|
|
9931
|
-
}
|
|
9932
10031
|
linkedSkillRows(
|
|
9933
10032
|
log,
|
|
9934
10033
|
baseline.updateMode.skillsLinked ?? [],
|
|
@@ -9982,29 +10081,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
9982
10081
|
)
|
|
9983
10082
|
);
|
|
9984
10083
|
}
|
|
9985
|
-
const policyEdited = baseline.updateMode.policyBackedUp.filter(
|
|
9986
|
-
(p2) => !noChecksum.has(`.claude/${p2}`)
|
|
9987
|
-
);
|
|
9988
|
-
if (policyEdited.length > 0) {
|
|
9989
|
-
log(
|
|
9990
|
-
assetRow(
|
|
9991
|
-
"skip",
|
|
9992
|
-
"edited policy files",
|
|
9993
|
-
`${policyEdited.length} backed up as *.backup-<time>`
|
|
9994
|
-
)
|
|
9995
|
-
);
|
|
9996
|
-
}
|
|
9997
|
-
hadFileRow(log, "policy files", displacedPolicy);
|
|
9998
|
-
const unmeasured = [...noChecksum].filter((p2) => p2 !== ".mcp-allowlist");
|
|
9999
|
-
if (unmeasured.length > 0) {
|
|
10000
|
-
log(
|
|
10001
|
-
assetRow(
|
|
10002
|
-
"skip",
|
|
10003
|
-
"no checksum on record",
|
|
10004
|
-
`${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`
|
|
10005
|
-
)
|
|
10006
|
-
);
|
|
10007
|
-
}
|
|
10008
10084
|
const retiredRefs = baseline.updateMode.retiredHookRefsRemoved ?? [];
|
|
10009
10085
|
if (retiredRefs.length > 0) {
|
|
10010
10086
|
log(
|
|
@@ -10054,17 +10130,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
10054
10130
|
)
|
|
10055
10131
|
);
|
|
10056
10132
|
}
|
|
10057
|
-
const externalEdited = baseline.updateMode.externalBackedUp.filter((p2) => !noChecksum.has(p2));
|
|
10058
|
-
if (externalEdited.length > 0) {
|
|
10059
|
-
log(
|
|
10060
|
-
assetRow(
|
|
10061
|
-
"skip",
|
|
10062
|
-
"edited external CLI files",
|
|
10063
|
-
`${externalEdited.length} backed up as *.backup-<time>`
|
|
10064
|
-
)
|
|
10065
|
-
);
|
|
10066
|
-
}
|
|
10067
|
-
hadFileRow(log, "external CLI files", displacedExternal);
|
|
10068
10133
|
const displacedSame = baseline.updateMode.displacedSame ?? [];
|
|
10069
10134
|
if (displacedSame.length > 0) {
|
|
10070
10135
|
log(
|
|
@@ -10525,7 +10590,7 @@ function renderInterruptedInstall(err, e2, cwd = process.cwd()) {
|
|
|
10525
10590
|
}
|
|
10526
10591
|
const { path, error } = e2.record;
|
|
10527
10592
|
err(
|
|
10528
|
-
path !== null ? ` Already in place before it stopped \u2014 recorded in ${
|
|
10593
|
+
path !== null ? ` Already in place before it stopped \u2014 recorded in ${relative7(cwd, path) || path}:` : " Already in place before it stopped:"
|
|
10529
10594
|
);
|
|
10530
10595
|
for (const [key, n] of groups) {
|
|
10531
10596
|
err(` ${key.endsWith("/") ? `${key} (${n} file${n === 1 ? "" : "s"})` : key}`);
|
|
@@ -10848,7 +10913,7 @@ function registerInstallCommand(cli) {
|
|
|
10848
10913
|
|
|
10849
10914
|
// src/commands/list.ts
|
|
10850
10915
|
init_esm_shims();
|
|
10851
|
-
import { existsSync as existsSync25, readFileSync as
|
|
10916
|
+
import { existsSync as existsSync25, readFileSync as readFileSync26 } from "fs";
|
|
10852
10917
|
import { join as join34, resolve as resolve3 } from "path";
|
|
10853
10918
|
function listAction(options = {}, deps = {}) {
|
|
10854
10919
|
const log = deps.log ?? console.log;
|
|
@@ -10953,7 +11018,7 @@ function formatHarnessFileRows(log, projectDir) {
|
|
|
10953
11018
|
const rootMd = log.templates.rootClaudeMd;
|
|
10954
11019
|
if (rootMd) {
|
|
10955
11020
|
const path = join34(projectDir, rootMd.path);
|
|
10956
|
-
const modified = existsSync25(path) && hashContent(
|
|
11021
|
+
const modified = existsSync25(path) && hashContent(readFileSync26(path, "utf8")) !== rootMd.sha256;
|
|
10957
11022
|
rows2.push(
|
|
10958
11023
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
10959
11024
|
);
|
|
@@ -10990,7 +11055,7 @@ import {
|
|
|
10990
11055
|
constants as fsConstants2,
|
|
10991
11056
|
lstatSync as lstatSync5,
|
|
10992
11057
|
readdirSync as readdirSync7,
|
|
10993
|
-
readFileSync as
|
|
11058
|
+
readFileSync as readFileSync27,
|
|
10994
11059
|
readlinkSync,
|
|
10995
11060
|
realpathSync as realpathSync3,
|
|
10996
11061
|
rmdirSync as rmdirSync3,
|
|
@@ -10998,7 +11063,7 @@ import {
|
|
|
10998
11063
|
statSync as statSync5,
|
|
10999
11064
|
writeFileSync as writeFileSync9
|
|
11000
11065
|
} from "fs";
|
|
11001
|
-
import { dirname as dirname8, join as join35, relative as
|
|
11066
|
+
import { dirname as dirname8, join as join35, relative as relative8, resolve as resolve4, sep as sep8 } from "path";
|
|
11002
11067
|
|
|
11003
11068
|
// src/uninstall-interactive.ts
|
|
11004
11069
|
init_esm_shims();
|
|
@@ -12087,7 +12152,8 @@ var TRACK_LABELS = {
|
|
|
12087
12152
|
"csr-supabase": "csr-supabase \u2014 Vite + React + Supabase",
|
|
12088
12153
|
"csr-fastify": "csr-fastify \u2014 Vite + React + Fastify",
|
|
12089
12154
|
"csr-fastapi": "csr-fastapi \u2014 Vite + React + FastAPI",
|
|
12090
|
-
"ssr-htmx": "ssr-htmx \u2014 htmx + FastAPI",
|
|
12155
|
+
"ssr-htmx": "ssr-htmx \u2014 htmx + FastAPI (Python) + daisyUI",
|
|
12156
|
+
"ssr-htmx-fastify": "ssr-htmx-fastify \u2014 htmx + Fastify (Node) + daisyUI",
|
|
12091
12157
|
"ssr-nextjs": "ssr-nextjs \u2014 Next.js (App Router)",
|
|
12092
12158
|
data: "data \u2014 Python data / DuckDB / PySide6",
|
|
12093
12159
|
executive: "executive \u2014 proposals / DD / pitch (no agent-skills)",
|
|
@@ -12926,12 +12992,7 @@ function settleLog(ctx, io) {
|
|
|
12926
12992
|
const recordDir = join35(projectDir, INSTALL_LOG_DIR);
|
|
12927
12993
|
if (outsideLinkAt(projectDir, INSTALL_LOG_DIR) !== null) io.rm(recordDir);
|
|
12928
12994
|
else {
|
|
12929
|
-
for (const rel of
|
|
12930
|
-
`${INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`,
|
|
12931
|
-
...HARNESS_RUNTIME_IGNORES
|
|
12932
|
-
]) {
|
|
12933
|
-
io.rm(join35(projectDir, rel));
|
|
12934
|
-
}
|
|
12995
|
+
for (const rel of RECORD_FILES) io.rm(join35(projectDir, rel));
|
|
12935
12996
|
pruneEmptyDirsUpward(projectDir, recordDir);
|
|
12936
12997
|
}
|
|
12937
12998
|
if (outsideLinkAt(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
|
|
@@ -13117,13 +13178,16 @@ function resolveCliTarget(ctx, io) {
|
|
|
13117
13178
|
function underAny(path, owned) {
|
|
13118
13179
|
return owned.some((o) => o.endsWith("/") ? path.startsWith(o) : path === o);
|
|
13119
13180
|
}
|
|
13120
|
-
function planCliRemoval(target, remaining) {
|
|
13181
|
+
function planCliRemoval(target, remaining, log, projectDir) {
|
|
13121
13182
|
const { exclusive, sharedNowUnowned } = removableFor(target, remaining);
|
|
13122
13183
|
const owned = [...exclusive, ...sharedNowUnowned];
|
|
13123
13184
|
const prefixes = owned.filter((o) => o.kind === "recorded" || o.kind === "anchor").map((o) => o.path);
|
|
13124
|
-
const
|
|
13185
|
+
const used = (path) => usersOf(path, projectDir).some((cli) => remaining.includes(cli));
|
|
13186
|
+
const has = (path) => underAny(path, prefixes) && !used(path);
|
|
13187
|
+
const kept = harnessCandidates(log).filter((p2) => underAny(p2, prefixes) && used(p2)).map((p2) => p2.split("/").slice(0, 3).join("/"));
|
|
13125
13188
|
return {
|
|
13126
13189
|
target,
|
|
13190
|
+
kept: [...new Set(kept)].sort(),
|
|
13127
13191
|
scope: {
|
|
13128
13192
|
has,
|
|
13129
13193
|
// `.claude/settings.json` 은 claude 의 `.claude/` 아래 · `.codex/config.toml` 은 codex 의 `.codex/` 아래 · `AGENTS.md` ·
|
|
@@ -13137,6 +13201,11 @@ function planCliRemoval(target, remaining) {
|
|
|
13137
13201
|
}
|
|
13138
13202
|
};
|
|
13139
13203
|
}
|
|
13204
|
+
function keptForRemainingLines(plan, preview) {
|
|
13205
|
+
if (plan.kept.length === 0) return [];
|
|
13206
|
+
const what = `${plan.kept.join(" \xB7 ")} \u2014 still used by Claude Code through its .claude/skills/ link(s)`;
|
|
13207
|
+
return [preview ? ` \u25CB leave ${what}` : ` ${c.yellow("\u2298")} left ${what}`];
|
|
13208
|
+
}
|
|
13140
13209
|
function removeCliHeader(target, installed, remaining) {
|
|
13141
13210
|
return [
|
|
13142
13211
|
"",
|
|
@@ -13158,6 +13227,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
|
|
|
13158
13227
|
harnessRoot
|
|
13159
13228
|
});
|
|
13160
13229
|
lines2.push(...harnessRemovalLines(preview, installLog, projectDir, plan.scope, true));
|
|
13230
|
+
lines2.push(...keptForRemainingLines(plan, true));
|
|
13161
13231
|
lines2.push(...assetsLeftLines(installLog, projectDir, plan.scope));
|
|
13162
13232
|
const rerender = agentsMdRerenderSet(plan.target, remaining, installLog, projectDir);
|
|
13163
13233
|
if (rerender !== null) {
|
|
@@ -13182,7 +13252,7 @@ function agentsMdRerenderSet(target, remaining, log, projectDir) {
|
|
|
13182
13252
|
}
|
|
13183
13253
|
function readTextOrEmpty(abs) {
|
|
13184
13254
|
try {
|
|
13185
|
-
return
|
|
13255
|
+
return readFileSync27(abs, "utf8");
|
|
13186
13256
|
} catch {
|
|
13187
13257
|
return "";
|
|
13188
13258
|
}
|
|
@@ -13267,7 +13337,7 @@ function removeCliAction(ctx, io) {
|
|
|
13267
13337
|
const { options, installLog, projectDir, harnessRoot } = ctx;
|
|
13268
13338
|
const installed = installedClis(installLog);
|
|
13269
13339
|
const remaining = installed.filter((cli) => cli !== target);
|
|
13270
|
-
const plan = planCliRemoval(target, remaining);
|
|
13340
|
+
const plan = planCliRemoval(target, remaining, installLog, projectDir);
|
|
13271
13341
|
for (const line of removeCliHeader(target, installed, remaining)) io.log(line);
|
|
13272
13342
|
if (options.dryRun) {
|
|
13273
13343
|
for (const line of removeCliDryRunLines(plan, ctx, remaining)) io.log(line);
|
|
@@ -13282,6 +13352,7 @@ function removeCliAction(ctx, io) {
|
|
|
13282
13352
|
for (const line of harnessRemovalLines(done, installLog, projectDir, plan.scope, false)) {
|
|
13283
13353
|
io.log(line);
|
|
13284
13354
|
}
|
|
13355
|
+
for (const line of keptForRemainingLines(plan, false)) io.log(line);
|
|
13285
13356
|
for (const line of assetsLeftLines(installLog, projectDir, plan.scope)) io.log(line);
|
|
13286
13357
|
const rerenderSet = agentsMdRerenderSet(target, remaining, installLog, projectDir);
|
|
13287
13358
|
const rerender = rerenderSet === null ? null : rerenderAgentsMd(ctx, rerenderSet);
|
|
@@ -13323,7 +13394,7 @@ function remnantFor(path, log, harnessRoot, projectDir, wiringGone = []) {
|
|
|
13323
13394
|
const gone = (k2) => wiringGone.includes(`hooks/${k2.split("#")[1] ?? ""}`);
|
|
13324
13395
|
return (disk) => {
|
|
13325
13396
|
try {
|
|
13326
|
-
const template =
|
|
13397
|
+
const template = readFileSync27(join35(harnessRoot, "templates", "settings.json"), "utf8");
|
|
13327
13398
|
const render = renderSettingsPortion(template, () => true);
|
|
13328
13399
|
return [...legacySettingsSeed(disk, render, projectDir, { edited: true }).keys()].filter(
|
|
13329
13400
|
(k2) => !k2.endsWith("{}") && !gone(k2)
|
|
@@ -13547,11 +13618,16 @@ var LEGACY_LOG = `${LEGACY_INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`;
|
|
|
13547
13618
|
var SKILLS_DIR3 = ".claude/skills/";
|
|
13548
13619
|
var CLI_FOLDERS2 = [".claude", ".codex", ".opencode"];
|
|
13549
13620
|
var LEFT_FILES_SHOWN = 3;
|
|
13621
|
+
var RECORD_FILES = [
|
|
13622
|
+
`${INSTALL_LOG_DIR}/${INSTALL_LOG_FILENAME}`,
|
|
13623
|
+
...HARNESS_RUNTIME_IGNORES
|
|
13624
|
+
];
|
|
13550
13625
|
var FULL_SCOPE = {
|
|
13551
13626
|
has: () => true,
|
|
13552
13627
|
shared: UNINSTALL_SHARED,
|
|
13553
13628
|
skeleton: PROJECT_SKELETON_DIRS,
|
|
13554
|
-
|
|
13629
|
+
// #817 — 기록 폴더도 — 헬퍼는 파일 하나씩 회수하고(#812) 설치자 파일은 남으니, 폴더가 왜 남았는지 같은 줄로 말한다
|
|
13630
|
+
folders: [...CLI_FOLDERS2, INSTALL_LOG_DIR],
|
|
13555
13631
|
importBlock: true,
|
|
13556
13632
|
anchor: true
|
|
13557
13633
|
};
|
|
@@ -13646,7 +13722,7 @@ function templateNext(harnessRoot, log, path) {
|
|
|
13646
13722
|
if (helper === void 0 && !path.startsWith(CLAUDE_DIR3)) return null;
|
|
13647
13723
|
try {
|
|
13648
13724
|
const source = helper ?? path.slice(CLAUDE_DIR3.length);
|
|
13649
|
-
return
|
|
13725
|
+
return readFileSync27(join35(harnessRoot, "templates", source), "utf8");
|
|
13650
13726
|
} catch {
|
|
13651
13727
|
return null;
|
|
13652
13728
|
}
|
|
@@ -13690,13 +13766,13 @@ function removeRecordedFile(path, ctx) {
|
|
|
13690
13766
|
return;
|
|
13691
13767
|
}
|
|
13692
13768
|
if (st2.isSymbolicLink()) effective = real;
|
|
13693
|
-
disk =
|
|
13769
|
+
disk = readFileSync27(effective, "utf8");
|
|
13694
13770
|
} catch {
|
|
13695
13771
|
out.unjudged.push(path);
|
|
13696
13772
|
return;
|
|
13697
13773
|
}
|
|
13698
13774
|
const rec = recorded(log, path, { isTarget: ctx.isTarget });
|
|
13699
|
-
const legacy = rec.state !== "sha" && isLegacyHarnessFile(path, hashContent(disk)) && (rec.state === "no-sha" || mayReclaimLegacy(log, path));
|
|
13775
|
+
const legacy = rec.state !== "sha" && isLegacyHarnessFile(path, hashContent(disk)) && (rec.state === "no-sha" || mayReclaimLegacy(log, projectDir, path));
|
|
13700
13776
|
const inBundle = legacy && ctx.isTarget(bundlePath(path));
|
|
13701
13777
|
const verdict = judge({
|
|
13702
13778
|
op: "remove",
|
|
@@ -13708,7 +13784,7 @@ function removeRecordedFile(path, ctx) {
|
|
|
13708
13784
|
if (verdict.verdict !== "remove" && verdict.verdict !== "backup+remove") return;
|
|
13709
13785
|
let backup = null;
|
|
13710
13786
|
if (verdict.verdict === "backup+remove") {
|
|
13711
|
-
backup = io.write ?
|
|
13787
|
+
backup = io.write ? relative8(projectDir, backupFile(effective)).split(sep8).join("/") : `${relative8(projectDir, effective).split(sep8).join("/")}.backup-<time>`;
|
|
13712
13788
|
}
|
|
13713
13789
|
if (io.write) {
|
|
13714
13790
|
if (effective !== abs) io.rm(abs);
|
|
@@ -13726,14 +13802,13 @@ function legacyFileCandidates() {
|
|
|
13726
13802
|
(p2) => p2.startsWith(SKILLS_DIR3) ? [p2, `.agents/skills/${p2.slice(SKILLS_DIR3.length)}`] : [p2]
|
|
13727
13803
|
);
|
|
13728
13804
|
}
|
|
13729
|
-
function
|
|
13730
|
-
return path.startsWith(".agents/skills/") ? `${SKILLS_DIR3}${path.slice(".agents/skills/".length)}` : path;
|
|
13731
|
-
}
|
|
13732
|
-
function mayReclaimLegacy(log, path) {
|
|
13805
|
+
function mayReclaimLegacy(log, projectDir, path) {
|
|
13733
13806
|
const clis = installedClis(log);
|
|
13734
|
-
if (!
|
|
13807
|
+
if (!usersOf(path, projectDir).some((c3) => clis.includes(c3))) return false;
|
|
13735
13808
|
if (isExcludedPath(bundlePath(path), excludedIds(log))) return false;
|
|
13736
|
-
return !(log.rootFiles ?? []).some(
|
|
13809
|
+
return !(log.rootFiles ?? []).some(
|
|
13810
|
+
(f) => f.path === path && f.change === "displaced" && !isLegacyDisplaced(log, projectDir, f)
|
|
13811
|
+
);
|
|
13737
13812
|
}
|
|
13738
13813
|
function removeLegacyHookWiring(log, projectDir, scope, io, removed) {
|
|
13739
13814
|
if (!scope.has(SETTINGS_TARGET) || !installedClis(log).includes("claude")) return [];
|
|
@@ -13816,7 +13891,7 @@ function removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom) {
|
|
|
13816
13891
|
return true;
|
|
13817
13892
|
}
|
|
13818
13893
|
if (lstatSync5(abs).isSymbolicLink()) effective = real;
|
|
13819
|
-
current =
|
|
13894
|
+
current = readFileSync27(effective, "utf8");
|
|
13820
13895
|
} catch {
|
|
13821
13896
|
out.unjudged.push(path);
|
|
13822
13897
|
return true;
|
|
@@ -14012,7 +14087,7 @@ function leftFolderLines(r, log, projectDir, scope, preview) {
|
|
|
14012
14087
|
const entries = /* @__PURE__ */ new Map();
|
|
14013
14088
|
for (const n of yoursUnder(projectDir, dir)) {
|
|
14014
14089
|
const full = `${dir}/${n.name}`;
|
|
14015
|
-
if (full === LEGACY_LOG) continue;
|
|
14090
|
+
if (full === LEGACY_LOG || RECORD_FILES.includes(full)) continue;
|
|
14016
14091
|
if (!gone.has(full) && !assetFiles.has(full) && !said(full)) entries.set(n.name, n.shown);
|
|
14017
14092
|
}
|
|
14018
14093
|
for (const p2 of back)
|
|
@@ -14079,7 +14154,7 @@ function removeAnchor(log, projectDir, io) {
|
|
|
14079
14154
|
if (outside !== null) return { removed: false, lines: outsideLines([outside], log) };
|
|
14080
14155
|
let disk;
|
|
14081
14156
|
try {
|
|
14082
|
-
disk =
|
|
14157
|
+
disk = readFileSync27(abs, "utf8");
|
|
14083
14158
|
} catch {
|
|
14084
14159
|
return {
|
|
14085
14160
|
removed: false,
|
|
@@ -14112,7 +14187,7 @@ function removeAnchor(log, projectDir, io) {
|
|
|
14112
14187
|
]
|
|
14113
14188
|
};
|
|
14114
14189
|
}
|
|
14115
|
-
const backup = edited ?
|
|
14190
|
+
const backup = edited ? relative8(projectDir, backupFile(abs)).split(sep8).join("/") : null;
|
|
14116
14191
|
io.rm(abs);
|
|
14117
14192
|
return {
|
|
14118
14193
|
removed: true,
|
|
@@ -14124,7 +14199,7 @@ function removeAnchor(log, projectDir, io) {
|
|
|
14124
14199
|
var AGENTS_MD2 = "AGENTS.md";
|
|
14125
14200
|
function readAgentsMdTemplate(harnessRoot) {
|
|
14126
14201
|
try {
|
|
14127
|
-
return
|
|
14202
|
+
return readFileSync27(join35(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
|
|
14128
14203
|
} catch {
|
|
14129
14204
|
return null;
|
|
14130
14205
|
}
|
|
@@ -14157,7 +14232,7 @@ function removeUnrecordedLegacyAgentsMd(path, projectDir, io, out, pruneFrom) {
|
|
|
14157
14232
|
const real = realpathSync3(abs);
|
|
14158
14233
|
if (!statSync5(real).isFile()) return false;
|
|
14159
14234
|
if (lstatSync5(abs).isSymbolicLink()) effective = real;
|
|
14160
|
-
current =
|
|
14235
|
+
current = readFileSync27(effective, "utf8");
|
|
14161
14236
|
} catch {
|
|
14162
14237
|
return false;
|
|
14163
14238
|
}
|
|
@@ -14175,7 +14250,7 @@ function removeLegacyAgentsMd(path, abs, effective, strip, ctx) {
|
|
|
14175
14250
|
let backup = `${path}.backup-<time>`;
|
|
14176
14251
|
try {
|
|
14177
14252
|
if (io.write) {
|
|
14178
|
-
backup =
|
|
14253
|
+
backup = relative8(projectDir, backupFile(effective)).split(sep8).join("/");
|
|
14179
14254
|
if (strip.remainder !== null) {
|
|
14180
14255
|
writeFileSync9(effective, neutralizeScaffoldBanner(strip.remainder), "utf8");
|
|
14181
14256
|
} else {
|
|
@@ -14219,9 +14294,9 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
14219
14294
|
continue;
|
|
14220
14295
|
}
|
|
14221
14296
|
try {
|
|
14222
|
-
if (hashContent(
|
|
14297
|
+
if (hashContent(readFileSync27(abs, "utf8")) !== sha256) {
|
|
14223
14298
|
const backup = backupFile(abs);
|
|
14224
|
-
notes.push(`backed up ${path} \u2192 ${
|
|
14299
|
+
notes.push(`backed up ${path} \u2192 ${relative8(projectDir, backup)} (changed since install)`);
|
|
14225
14300
|
}
|
|
14226
14301
|
rm(abs);
|
|
14227
14302
|
removed.push(path);
|
|
@@ -14236,8 +14311,8 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
14236
14311
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
14237
14312
|
let cursor = startDir;
|
|
14238
14313
|
while (true) {
|
|
14239
|
-
const rel =
|
|
14240
|
-
if (rel === "" || rel.startsWith(`..${
|
|
14314
|
+
const rel = relative8(projectDir, cursor);
|
|
14315
|
+
if (rel === "" || rel.startsWith(`..${sep8}`) || rel === "..") return;
|
|
14241
14316
|
try {
|
|
14242
14317
|
if (readdirSync7(cursor).length > 0) return;
|
|
14243
14318
|
rmdirSync3(cursor);
|
|
@@ -14267,7 +14342,7 @@ function readRootClaudeMd(projectDir) {
|
|
|
14267
14342
|
const path = join35(projectDir, "CLAUDE.md");
|
|
14268
14343
|
if (!existsSync26(path)) return null;
|
|
14269
14344
|
try {
|
|
14270
|
-
return
|
|
14345
|
+
return readFileSync27(path, "utf8");
|
|
14271
14346
|
} catch {
|
|
14272
14347
|
return null;
|
|
14273
14348
|
}
|