@uzysjung/agent-harness 26.170.0 → 26.171.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/{chunk-MGFWOWF4.js → chunk-U4X4Z333.js} +2 -49
- package/dist/chunk-U4X4Z333.js.map +1 -0
- package/dist/index.js +351 -525
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/templates/agents/reviewer.md +10 -62
- package/dist/chunk-MGFWOWF4.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -48,10 +48,7 @@ import {
|
|
|
48
48
|
claimBackupDir,
|
|
49
49
|
claudeRecorded,
|
|
50
50
|
claudeTargets,
|
|
51
|
-
collectPolicyHashes,
|
|
52
|
-
collectSkillHashes,
|
|
53
51
|
continuousNoteEnd,
|
|
54
|
-
copyDir,
|
|
55
52
|
copyFile,
|
|
56
53
|
corruptedInstallLogMessage,
|
|
57
54
|
createOutsideGuard,
|
|
@@ -71,7 +68,6 @@ import {
|
|
|
71
68
|
isAssetSelected,
|
|
72
69
|
isCliBase,
|
|
73
70
|
isCliNeutralTarget,
|
|
74
|
-
isHarnessOwned,
|
|
75
71
|
isInstallScope,
|
|
76
72
|
isKeyId,
|
|
77
73
|
isOutsideProject,
|
|
@@ -105,7 +101,7 @@ import {
|
|
|
105
101
|
targetsInclude,
|
|
106
102
|
upsertHarnessImport,
|
|
107
103
|
writeInstallLog
|
|
108
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-U4X4Z333.js";
|
|
109
105
|
|
|
110
106
|
// node_modules/sisteransi/src/index.js
|
|
111
107
|
var require_src = __commonJS({
|
|
@@ -1200,6 +1196,13 @@ var FILES = `
|
|
|
1200
1196
|
.claude/skills/verification-loop/SKILL.md 1744f867f7868b83b480341c2e8d08bff3848c2408255871d3aba0c2f7eff2f2
|
|
1201
1197
|
.claude/skills/verification-loop/agents/openai.yaml 1907fd04fea5c83d15faf14acfc978de90076d4351341cf9259639aa0c3b7bd3
|
|
1202
1198
|
.claude/skills/verification-loop/references/tracks.md de11792d447c2e8ea67e8367c6655bd94646b1ba3f29184c4521cae01facc6ec
|
|
1199
|
+
.uzys-agent-harness/check-absence.sh b9bbc7bbdc69d51eac2a4e4748ee7c137dd585b4f3ca1d9bd2404eab9694c9b4
|
|
1200
|
+
.uzys-agent-harness/check-absence.sh c08d3a1df4188e5f37ddf7f7b53901184d87bdbed63c5c99afbddd0ef6a0442d
|
|
1201
|
+
.uzys-agent-harness/check-absence.sh 3655faf030e319e26a6386b316df004f4e2fee963fe0ebacb661030e85cda92e
|
|
1202
|
+
.uzys-agent-harness/protect-branch.sh 3356c4941ef1d5c174b10fd8858887f00b3b06c851788064e0e3cc6a6c8c56fd
|
|
1203
|
+
.uzys-agent-harness/protect-branch.sh ecd46450beae58c085a0d407c34441ae470092d9963bd4a37f09416a22cfad35
|
|
1204
|
+
.uzys-agent-harness/spec-drift-check.sh f35a64dd9954a776c3eabf356c0cda017db30e129d08022341a07c5a9d2b6f9a
|
|
1205
|
+
.uzys-agent-harness/spec-drift-check.sh 3c6925b03775454ec3a81e63dda400511824a9ac6e3f8f30b302ce1edf39d062
|
|
1203
1206
|
`;
|
|
1204
1207
|
var LEGACY_HARNESS_FILES = (() => {
|
|
1205
1208
|
const m = /* @__PURE__ */ new Map();
|
|
@@ -1535,13 +1538,14 @@ function planUpsert(args) {
|
|
|
1535
1538
|
}
|
|
1536
1539
|
function planStrip(args) {
|
|
1537
1540
|
const alwaysStrip2 = args.alwaysStrip ?? (() => false);
|
|
1538
|
-
const plan = { remove: [], kept: [], portions: /* @__PURE__ */ new Map() };
|
|
1541
|
+
const plan = { remove: [], removedEdited: [], kept: [], portions: /* @__PURE__ */ new Map() };
|
|
1539
1542
|
for (const [key, sha] of args.recorded) {
|
|
1540
1543
|
if (args.excluded.has(key)) continue;
|
|
1541
1544
|
const now = args.present.get(key);
|
|
1542
1545
|
if (now === void 0) continue;
|
|
1543
1546
|
if (now === sha || alwaysStrip2(key)) {
|
|
1544
1547
|
plan.remove.push(key);
|
|
1548
|
+
if (now !== sha) plan.removedEdited.push(key);
|
|
1545
1549
|
} else {
|
|
1546
1550
|
plan.kept.push(key);
|
|
1547
1551
|
plan.portions.set(key, sha);
|
|
@@ -1827,6 +1831,7 @@ var jsonKeys = {
|
|
|
1827
1831
|
text: changed ? serialize(root) : existing,
|
|
1828
1832
|
changed,
|
|
1829
1833
|
removed: plan.remove,
|
|
1834
|
+
removedEdited: plan.removedEdited,
|
|
1830
1835
|
kept: plan.kept,
|
|
1831
1836
|
portions: plan.portions,
|
|
1832
1837
|
empty: Object.keys(root).length === 0
|
|
@@ -2018,6 +2023,7 @@ var lines = {
|
|
|
2018
2023
|
text,
|
|
2019
2024
|
changed: text !== existing,
|
|
2020
2025
|
removed: plan.remove,
|
|
2026
|
+
removedEdited: plan.removedEdited,
|
|
2021
2027
|
kept: plan.kept,
|
|
2022
2028
|
portions: plan.portions,
|
|
2023
2029
|
empty: text.trim() === ""
|
|
@@ -2472,6 +2478,7 @@ var markerMd = {
|
|
|
2472
2478
|
text,
|
|
2473
2479
|
changed: text !== existing,
|
|
2474
2480
|
removed: plan.remove,
|
|
2481
|
+
removedEdited: plan.removedEdited,
|
|
2475
2482
|
kept: plan.kept,
|
|
2476
2483
|
portions: plan.portions,
|
|
2477
2484
|
empty: text.trim() === ""
|
|
@@ -3508,6 +3515,7 @@ var tomlRegion = {
|
|
|
3508
3515
|
text,
|
|
3509
3516
|
changed: text !== existing,
|
|
3510
3517
|
removed: plan.remove,
|
|
3518
|
+
removedEdited: plan.removedEdited,
|
|
3511
3519
|
kept: plan.kept,
|
|
3512
3520
|
portions: plan.portions,
|
|
3513
3521
|
empty: text.trim() === ""
|
|
@@ -3840,7 +3848,7 @@ function harnessWrite(input) {
|
|
|
3840
3848
|
const update = input.run === "update";
|
|
3841
3849
|
if (input.excluded) return j("leave", "", "keep");
|
|
3842
3850
|
if (disk === null) {
|
|
3843
|
-
if (rec.state
|
|
3851
|
+
if (rec.state !== "sha") return j("create", update ? LINES.newInRelease : LINES.wrote, "sha");
|
|
3844
3852
|
return j("create", update ? LINES.restored : LINES.wrote, "sha");
|
|
3845
3853
|
}
|
|
3846
3854
|
if (rec.state === "none") {
|
|
@@ -4260,7 +4268,11 @@ function scanFieldOf(log, path) {
|
|
|
4260
4268
|
({ dir, ext }) => path.startsWith(`${CLAUDE_DIR}${dir}/`) && path.endsWith(ext)
|
|
4261
4269
|
);
|
|
4262
4270
|
if (policy === void 0) return "outside";
|
|
4263
|
-
return present2(
|
|
4271
|
+
return present2(
|
|
4272
|
+
(log.policyFiles ?? []).some(
|
|
4273
|
+
(f) => f.path.startsWith(`${policy.dir}/`) && !RETIRED_PATHS.includes(f.path)
|
|
4274
|
+
)
|
|
4275
|
+
);
|
|
4264
4276
|
}
|
|
4265
4277
|
function isExternalOnly(path) {
|
|
4266
4278
|
const owners = ownersOf(path);
|
|
@@ -4296,6 +4308,7 @@ function createInstallWriter(args) {
|
|
|
4296
4308
|
const portionPaths = [];
|
|
4297
4309
|
const backups = [];
|
|
4298
4310
|
const judged = [];
|
|
4311
|
+
const writes = [];
|
|
4299
4312
|
const shared2 = [];
|
|
4300
4313
|
const outside = createOutsideGuard(projectDir);
|
|
4301
4314
|
const rel = (abs) => relative(projectDir, abs).split(sep).join("/");
|
|
@@ -4310,12 +4323,15 @@ function createInstallWriter(args) {
|
|
|
4310
4323
|
if (outside.skip(abs)) return null;
|
|
4311
4324
|
const next = "source" in from ? readFileSync4(from.source, "utf8") : from.content;
|
|
4312
4325
|
const disk = existsSync6(abs) ? readFileSync4(abs, "utf8") : null;
|
|
4326
|
+
const rec = recorded(previousLog, path, opts);
|
|
4327
|
+
const diskSha = rec.state === "no-sha" && disk !== null ? hashContent(disk) : null;
|
|
4313
4328
|
const j4 = judge({
|
|
4314
4329
|
op: "write",
|
|
4315
4330
|
kind: "harness",
|
|
4316
|
-
rec:
|
|
4331
|
+
rec: diskSha !== null && isLegacyHarnessFile(path, diskSha) ? { state: "sha", sha256: diskSha } : rec,
|
|
4317
4332
|
disk,
|
|
4318
|
-
next
|
|
4333
|
+
next,
|
|
4334
|
+
...args.run === void 0 ? {} : { run: args.run }
|
|
4319
4335
|
});
|
|
4320
4336
|
let backupAbs;
|
|
4321
4337
|
const write = () => {
|
|
@@ -4357,13 +4373,11 @@ function createInstallWriter(args) {
|
|
|
4357
4373
|
default:
|
|
4358
4374
|
throw new Error(`install: '${j4.record}' is not a write record (${path})`);
|
|
4359
4375
|
}
|
|
4376
|
+
const line = backup === void 0 ? j4.line : j4.line.replace(BACKUP_PLACEHOLDER, backup);
|
|
4377
|
+
const backupPair = backup === void 0 || backupAbs === void 0 ? {} : { backup, backupAbs };
|
|
4378
|
+
writes.push({ path, verdict: j4.verdict, record: j4.record, line, ...backupPair });
|
|
4360
4379
|
if (j4.line === "" || j4.verdict === "create" || j4.verdict === "overwrite") return null;
|
|
4361
|
-
const out = {
|
|
4362
|
-
path,
|
|
4363
|
-
verdict: j4.verdict,
|
|
4364
|
-
line: backup === void 0 ? j4.line : j4.line.replace(BACKUP_PLACEHOLDER, backup),
|
|
4365
|
-
...backup === void 0 || backupAbs === void 0 ? {} : { backup, backupAbs }
|
|
4366
|
-
};
|
|
4380
|
+
const out = { path, verdict: j4.verdict, line, ...backupPair };
|
|
4367
4381
|
judged.push(out);
|
|
4368
4382
|
return out;
|
|
4369
4383
|
}
|
|
@@ -4424,7 +4438,12 @@ function createInstallWriter(args) {
|
|
|
4424
4438
|
if (disk === null && ![...res.portions.keys()].some((k2) => !isContainerKey(k2))) {
|
|
4425
4439
|
return result("leave", "");
|
|
4426
4440
|
}
|
|
4441
|
+
let backupAbs;
|
|
4427
4442
|
if (res.changed) {
|
|
4443
|
+
if (disk !== null && res.removedEdited.length > 0) {
|
|
4444
|
+
backupAbs = backupFile(abs, now);
|
|
4445
|
+
backups.push(backupAbs);
|
|
4446
|
+
}
|
|
4428
4447
|
mkdirSync(dirname(abs), { recursive: true });
|
|
4429
4448
|
writeFileSync3(abs, res.text);
|
|
4430
4449
|
}
|
|
@@ -4446,7 +4465,7 @@ function createInstallWriter(args) {
|
|
|
4446
4465
|
const ids = (keys) => [...keys].flatMap((k2) => keyId(path, k2) ?? []);
|
|
4447
4466
|
const out = excludedKeys(path, excluded);
|
|
4448
4467
|
const removedOut = res.removed.filter((k2) => out.has(k2));
|
|
4449
|
-
const removedEdited = res.removedEdited
|
|
4468
|
+
const removedEdited = res.removedEdited;
|
|
4450
4469
|
const keptOut = res.kept.filter((k2) => out.has(k2));
|
|
4451
4470
|
const onlyRemoved = disk !== null && res.removed.length > 0 && res.replaced.length === 0 && addedKeys.length === 0 && restoredKeys.size === 0;
|
|
4452
4471
|
const wrote = onlyRemoved ? "removed the harness part" : j4.line;
|
|
@@ -4454,6 +4473,7 @@ function createInstallWriter(args) {
|
|
|
4454
4473
|
const retired = names(res.removed.filter((k2) => !out.has(k2) && !render.has(k2)));
|
|
4455
4474
|
const replaced = names(res.replaced);
|
|
4456
4475
|
return result(j4.verdict, line, {
|
|
4476
|
+
...backupAbs === void 0 ? {} : { backup: rel(backupAbs), backupAbs },
|
|
4457
4477
|
...retired.length > 0 ? { retired } : {},
|
|
4458
4478
|
...replaced.length > 0 ? { replaced } : {},
|
|
4459
4479
|
changed: res.changed,
|
|
@@ -4481,6 +4501,7 @@ function createInstallWriter(args) {
|
|
|
4481
4501
|
portionPaths: [...portionPaths],
|
|
4482
4502
|
backups: [...backups],
|
|
4483
4503
|
judged: [...judged],
|
|
4504
|
+
writes: [...writes],
|
|
4484
4505
|
shared: [...shared2],
|
|
4485
4506
|
outside: outside.list()
|
|
4486
4507
|
})
|
|
@@ -4770,7 +4791,7 @@ function legacyGitignoreSeed(text, render, previous) {
|
|
|
4770
4791
|
// src/installer.ts
|
|
4771
4792
|
init_esm_shims();
|
|
4772
4793
|
import { chmodSync as chmodSync3, existsSync as existsSync22, readdirSync as readdirSync5, readFileSync as readFileSync22, writeFileSync as writeFileSync8 } from "fs";
|
|
4773
|
-
import { basename as basename4, join as join31, relative as
|
|
4794
|
+
import { basename as basename4, join as join31, relative as relative5, resolve, sep as sep6 } from "path";
|
|
4774
4795
|
|
|
4775
4796
|
// src/anchor-seed.ts
|
|
4776
4797
|
init_esm_shims();
|
|
@@ -5049,41 +5070,34 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
5049
5070
|
return false;
|
|
5050
5071
|
}
|
|
5051
5072
|
if (outside.skip(absPath)) return false;
|
|
5052
|
-
const
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5073
|
+
const recordedSha = baseline.get(rel);
|
|
5074
|
+
const rec = recordedSha !== void 0 ? { state: "sha", sha256: recordedSha } : { state: owned(rel) ? "no-sha" : "none" };
|
|
5075
|
+
const disk = existsSync9(absPath) ? readFileSync6(absPath, "utf8") : null;
|
|
5076
|
+
if (disk === null && refreshOnly && !opts?.createInRefresh && rec.state !== "sha")
|
|
5077
|
+
return false;
|
|
5078
|
+
const { verdict, record } = judge({ op: "write", kind: "harness", rec, disk, next: content });
|
|
5079
|
+
let backupRel = null;
|
|
5080
|
+
if (verdict === "backup+overwrite" || verdict === "overwrite" && opts?.backupFirst === true) {
|
|
5081
|
+
const backup = backupFile(absPath, now);
|
|
5082
|
+
backupPaths.push(backup);
|
|
5083
|
+
journal?.backup(backup);
|
|
5084
|
+
backupRel = relative2(projectDir, backup).split(sep3).join("/");
|
|
5085
|
+
if (verdict === "backup+overwrite" && rec.state !== "none") backedUp.push(rel);
|
|
5086
|
+
}
|
|
5087
|
+
if (record === "displaced" || record === "displaced+sha") {
|
|
5088
|
+
displace({ path: rel, change: "displaced", notes: backupRel === null ? [] : [backupRel] });
|
|
5089
|
+
}
|
|
5090
|
+
if (verdict !== "leave") {
|
|
5056
5091
|
mkdirSync2(dirname2(absPath), { recursive: true });
|
|
5057
5092
|
writeFileSync4(absPath, content);
|
|
5058
5093
|
updated++;
|
|
5059
|
-
if (refreshOnly &&
|
|
5060
|
-
}
|
|
5061
|
-
|
|
5062
|
-
const
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
return true;
|
|
5066
|
-
}
|
|
5067
|
-
if (current !== content) {
|
|
5068
|
-
if (!isHarnessOwned(baseline, rel, current)) {
|
|
5069
|
-
const backup = backupFile(absPath, now);
|
|
5070
|
-
backupPaths.push(backup);
|
|
5071
|
-
journal?.backup(backup);
|
|
5072
|
-
if (firstContact) {
|
|
5073
|
-
const backupRel = relative2(projectDir, backup).split(sep3).join("/");
|
|
5074
|
-
displace({ path: rel, change: "displaced", notes: [backupRel] });
|
|
5075
|
-
} else backedUp.push(rel);
|
|
5076
|
-
} else if (opts?.backupFirst === true) {
|
|
5077
|
-
const backup = backupFile(absPath, now);
|
|
5078
|
-
backupPaths.push(backup);
|
|
5079
|
-
journal?.backup(backup);
|
|
5080
|
-
}
|
|
5081
|
-
writeFileSync4(absPath, content);
|
|
5082
|
-
updated++;
|
|
5083
|
-
}
|
|
5094
|
+
if (refreshOnly && verdict === "create" && rec.state === "sha") restored.push(rel);
|
|
5095
|
+
}
|
|
5096
|
+
if (record === "sha" || record === "displaced+sha") {
|
|
5097
|
+
const digest = hashContent(content);
|
|
5098
|
+
written.set(rel, digest);
|
|
5099
|
+
journal?.file({ path: rel, sha256: digest });
|
|
5084
5100
|
}
|
|
5085
|
-
written.set(rel, digest);
|
|
5086
|
-
journal?.file({ path: rel, sha256: digest });
|
|
5087
5101
|
return true;
|
|
5088
5102
|
},
|
|
5089
5103
|
...journal === void 0 ? {} : { journal },
|
|
@@ -5158,7 +5172,7 @@ function renderRulesBlock(rules) {
|
|
|
5158
5172
|
// src/shared-write.ts
|
|
5159
5173
|
init_esm_shims();
|
|
5160
5174
|
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
5161
|
-
import { dirname as dirname3, join as join13 } from "path";
|
|
5175
|
+
import { dirname as dirname3, join as join13, relative as relative3, sep as sep4 } from "path";
|
|
5162
5176
|
function toPortions(path, portions) {
|
|
5163
5177
|
const adapter = adapterFor(path);
|
|
5164
5178
|
if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
|
|
@@ -5396,6 +5410,10 @@ function stripShared(params) {
|
|
|
5396
5410
|
});
|
|
5397
5411
|
}
|
|
5398
5412
|
const fileRemoved = params.removeIfEmpty === true && res.removed.length > 0 && res.empty;
|
|
5413
|
+
const edited = res.removedEdited.length === 0 ? void 0 : {
|
|
5414
|
+
keys: res.removedEdited,
|
|
5415
|
+
backup: params.write ? relative3(projectDir, backupFile(abs)).split(sep4).join("/") : `${path}.backup-<time>`
|
|
5416
|
+
};
|
|
5399
5417
|
if (params.write && fileRemoved) rmSync(abs);
|
|
5400
5418
|
else if (params.write && res.changed) writeFileSync5(abs, res.text);
|
|
5401
5419
|
const action = res.removed.length > 0 ? "removed" : res.kept.length > 0 ? "left" : "none";
|
|
@@ -5403,6 +5421,7 @@ function stripShared(params) {
|
|
|
5403
5421
|
removed: res.removed,
|
|
5404
5422
|
kept: res.kept,
|
|
5405
5423
|
portions: toPortions(path, res.portions),
|
|
5424
|
+
...edited === void 0 ? {} : { edited },
|
|
5406
5425
|
...fileRemoved ? { fileRemoved } : {}
|
|
5407
5426
|
});
|
|
5408
5427
|
}
|
|
@@ -6453,7 +6472,7 @@ import {
|
|
|
6453
6472
|
rmSync as rmSync3
|
|
6454
6473
|
} from "fs";
|
|
6455
6474
|
import { homedir as homedir2 } from "os";
|
|
6456
|
-
import { dirname as dirname6, join as join25, relative as
|
|
6475
|
+
import { dirname as dirname6, join as join25, relative as relative4, sep as sep5 } from "path";
|
|
6457
6476
|
var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
|
|
6458
6477
|
function skillRootOf(cli) {
|
|
6459
6478
|
return cli === "claude" ? ".claude/skills" : ".agents/skills";
|
|
@@ -6815,7 +6834,7 @@ function pruneEmptyDirs(dir) {
|
|
|
6815
6834
|
}
|
|
6816
6835
|
}
|
|
6817
6836
|
function toProjectRel(projectDir, abs) {
|
|
6818
|
-
return
|
|
6837
|
+
return relative4(projectDir, abs).split(sep5).join("/");
|
|
6819
6838
|
}
|
|
6820
6839
|
function recordRefreshedFiles(projectDir, log, report, displaced, writeLog) {
|
|
6821
6840
|
const next = new Map(
|
|
@@ -7200,8 +7219,6 @@ init_esm_shims();
|
|
|
7200
7219
|
import {
|
|
7201
7220
|
copyFileSync,
|
|
7202
7221
|
existsSync as existsSync21,
|
|
7203
|
-
lstatSync as lstatSync5,
|
|
7204
|
-
mkdirSync as mkdirSync7,
|
|
7205
7222
|
readdirSync as readdirSync4,
|
|
7206
7223
|
readFileSync as readFileSync21,
|
|
7207
7224
|
rmdirSync as rmdirSync2,
|
|
@@ -7268,91 +7285,12 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7268
7285
|
demotedAgents: [],
|
|
7269
7286
|
mcpAllowlistRetired: null
|
|
7270
7287
|
};
|
|
7271
|
-
const fresh = installNewAssets(projectDir, templatesDir, wants, outside);
|
|
7272
|
-
report.installedNew = fresh.installed;
|
|
7273
|
-
report.restored = fresh.restored;
|
|
7274
7288
|
const logAtStart = readInstallLog(projectDir);
|
|
7275
7289
|
const claudeManaged = logAtStart === null || installedClis(logAtStart).includes("claude");
|
|
7276
|
-
const recordOf = recordedStateOf(logAtStart);
|
|
7277
|
-
const claudeDisplaced = [];
|
|
7278
7290
|
report.claudeUnrecorded = !claudeManaged && logAtStart !== null && existsSync21(claudeDir) ? recordClaudeCommand(logAtStart, installedTracks(projectDir)) : null;
|
|
7279
|
-
const
|
|
7280
|
-
const
|
|
7281
|
-
|
|
7282
|
-
if (!claudeManaged || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
|
|
7283
|
-
const target = join30(claudeDir, dir);
|
|
7284
|
-
const source = join30(templatesDir, dir);
|
|
7285
|
-
const label = `.claude/${dir}`;
|
|
7286
|
-
const ctx = {
|
|
7287
|
-
prefix: dir,
|
|
7288
|
-
baseline: policyBase,
|
|
7289
|
-
outside,
|
|
7290
|
-
excluded: excludedIds(logAtStart),
|
|
7291
|
-
// 0단계가 방금 깐 파일은 기록 전이지만 하네스 것이다 — 첫 접촉으로 읽지 않는다
|
|
7292
|
-
justWritten: new Set(fresh.installed),
|
|
7293
|
-
...recordOf === void 0 ? {} : { recordOf }
|
|
7294
|
-
};
|
|
7295
|
-
const synced = updateDir(target, source, ext, ctx);
|
|
7296
|
-
report.updated[label] = synced.updated;
|
|
7297
|
-
report.policyBackedUp.push(...synced.backedUp);
|
|
7298
|
-
claudeDisplaced.push(...synced.displaced);
|
|
7299
|
-
report.pruned[label] = pruneOrphans(target, source, ext, ctx);
|
|
7300
|
-
for (const f of report.pruned[label]) prunedPaths.add(`${label}/${f}`);
|
|
7301
|
-
}
|
|
7302
|
-
const syncedDirs = POLICY_DIRS.filter((d2) => wants(d2.dir === "hooks" ? "hooks" : "rules")).map(
|
|
7303
|
-
(d2) => d2.dir
|
|
7304
|
-
);
|
|
7305
|
-
if (claudeManaged && syncedDirs.length > 0)
|
|
7306
|
-
refreshPolicyBaseline(
|
|
7307
|
-
projectDir,
|
|
7308
|
-
templatesDir,
|
|
7309
|
-
syncedDirs,
|
|
7310
|
-
outsidePaths(outside, ".claude/"),
|
|
7311
|
-
notOursUnder(claudeDisplaced, ".claude/")
|
|
7312
|
-
);
|
|
7313
|
-
const prunedRestored = logAtStart === null || prunedPaths.size === 0 ? [] : restoreDisplaced(logAtStart, projectDir, { has: (p2) => prunedPaths.has(p2), write: true });
|
|
7314
|
-
const skillTargets = logAtStart === null ? void 0 : recordedSkillIds(projectDir, logAtStart);
|
|
7315
|
-
const skillSync = claudeManaged && wants("skills") ? syncSkills(
|
|
7316
|
-
join30(claudeDir, "skills"),
|
|
7317
|
-
join30(templatesDir, "skills"),
|
|
7318
|
-
skillBaseline(projectDir),
|
|
7319
|
-
/* @__PURE__ */ new Date(),
|
|
7320
|
-
(relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`),
|
|
7321
|
-
outside,
|
|
7322
|
-
excludedSkillOf(logAtStart),
|
|
7323
|
-
{
|
|
7324
|
-
...recordOf === void 0 ? {} : { recordOf },
|
|
7325
|
-
...skillTargets === void 0 ? {} : { targets: skillTargets }
|
|
7326
|
-
}
|
|
7327
|
-
) : {
|
|
7328
|
-
updated: 0,
|
|
7329
|
-
backedUp: [],
|
|
7330
|
-
skippedLinks: [],
|
|
7331
|
-
foreignOwned: [],
|
|
7332
|
-
pruned: [],
|
|
7333
|
-
displaced: []
|
|
7334
|
-
};
|
|
7335
|
-
claudeDisplaced.push(...skillSync.displaced);
|
|
7336
|
-
if (wants("skills")) report.updated[".claude/skills"] = skillSync.updated;
|
|
7337
|
-
report.skillsBackedUp = skillSync.backedUp;
|
|
7338
|
-
report.skillsSkippedLinks = skillSync.skippedLinks;
|
|
7339
|
-
report.skillsPruned = skillSync.pruned;
|
|
7340
|
-
const newSkills = claudeManaged && wants("new-skills") ? installNewSkillDirs(projectDir, templatesDir, installedTracks(projectDir), outside) : { installed: [], restored: [], foreignOwned: [] };
|
|
7341
|
-
report.installedNew.push(...newSkills.installed);
|
|
7342
|
-
report.restored.push(...newSkills.restored);
|
|
7343
|
-
const copiedSkills = [...newSkills.installed, ...newSkills.restored];
|
|
7344
|
-
if (claudeManaged && wants("skills"))
|
|
7345
|
-
refreshSkillBaseline(
|
|
7346
|
-
projectDir,
|
|
7347
|
-
templatesDir,
|
|
7348
|
-
outsidePaths(outside, ".claude/skills/"),
|
|
7349
|
-
skillTargets,
|
|
7350
|
-
notOursUnder(claudeDisplaced, ".claude/skills/")
|
|
7351
|
-
);
|
|
7352
|
-
else if (claudeManaged && copiedSkills.length > 0)
|
|
7353
|
-
recordNewSkillBaseline(projectDir, templatesDir, copiedSkills);
|
|
7354
|
-
if (claudeDisplaced.length > 0 || prunedRestored.length > 0)
|
|
7355
|
-
recordDisplaced(projectDir, claudeDisplaced, prunedRestored);
|
|
7291
|
+
const harness = logAtStart === null ? { displaced: [], restored: [], foreignOwned: [], outside: [] } : updateHarnessFiles({ projectDir, templatesDir, log: logAtStart, wants, outside, report });
|
|
7292
|
+
const claudeDisplaced = harness.displaced;
|
|
7293
|
+
const prunedRestored = harness.restored;
|
|
7356
7294
|
if (existsSync21(claudeDir) && claudeManaged) {
|
|
7357
7295
|
if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report, outside);
|
|
7358
7296
|
}
|
|
@@ -7388,8 +7326,6 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7388
7326
|
if (wants("hooks")) report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
7389
7327
|
report.retiredAgents = staleAgentFiles(claudeDir);
|
|
7390
7328
|
report.demotedAgents = demotedAgentFiles(claudeDir, installedTracks(projectDir));
|
|
7391
|
-
if (logAtStart !== null && wants("rules"))
|
|
7392
|
-
refreshNeutralHelpers(projectDir, templatesDir, report);
|
|
7393
7329
|
const external = wants("external") ? refreshExternalCli(projectDir, harnessRoot, legacyDroppedKeys(logAtStart), harnessMcp) : {
|
|
7394
7330
|
externalUpdated: 0,
|
|
7395
7331
|
externalBackedUp: [],
|
|
@@ -7461,12 +7397,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7461
7397
|
report.restoredWithout = restoredWithoutArgs(report.restored, installedTracks(projectDir));
|
|
7462
7398
|
const saidBySlotRow = new Set(report.skillsSkippedLinks.map((id) => `.claude/skills/${id}`));
|
|
7463
7399
|
const merged = [];
|
|
7464
|
-
for (const f of [
|
|
7465
|
-
...skillSync.foreignOwned,
|
|
7466
|
-
...fresh.foreignOwned,
|
|
7467
|
-
...newSkills.foreignOwned,
|
|
7468
|
-
...external.externalForeignOwned
|
|
7469
|
-
]) {
|
|
7400
|
+
for (const f of [...harness.foreignOwned, ...external.externalForeignOwned]) {
|
|
7470
7401
|
if (!saidBySlotRow.has(f) && !merged.includes(f)) merged.push(f);
|
|
7471
7402
|
}
|
|
7472
7403
|
report.foreignOwned = merged;
|
|
@@ -7496,7 +7427,12 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
|
7496
7427
|
).filter((id) => !skillRefresh.notInCatalog.includes(id))
|
|
7497
7428
|
].filter((id) => !saidAsRemoved.has(id));
|
|
7498
7429
|
report.externalSkillsUnknown = skillRefresh.unknown;
|
|
7499
|
-
const outsideLinks = mergeOutside(
|
|
7430
|
+
const outsideLinks = mergeOutside(
|
|
7431
|
+
harness.outside,
|
|
7432
|
+
outside.list(),
|
|
7433
|
+
sharedWrites.outside,
|
|
7434
|
+
external.externalOutside
|
|
7435
|
+
);
|
|
7500
7436
|
if (outsideLinks.length > 0) report.outsideLinks = outsideLinks;
|
|
7501
7437
|
const displacedAll = [...claudeDisplaced, ...external.displaced];
|
|
7502
7438
|
const displacedMoved = displacedAll.filter((f) => f.notes.length > 0).map((f) => f.path);
|
|
@@ -7590,7 +7526,9 @@ function checksumOnRecord(log) {
|
|
|
7590
7526
|
...(log.policyFiles ?? []).map((f) => `.claude/${f.path}`),
|
|
7591
7527
|
...(log.skillFiles ?? []).map((f) => `.claude/skills/${f.path}`),
|
|
7592
7528
|
...(log.externalFiles ?? []).map((f) => f.path),
|
|
7593
|
-
...log.assets.flatMap((a) => a.files ?? []).map((f) => f.path)
|
|
7529
|
+
...log.assets.flatMap((a) => a.files ?? []).map((f) => f.path),
|
|
7530
|
+
// #800 — 함께 쓰는 파일은 몫마다 sha 가 있다 — 그 백업은 설치자 편집이 확실하다
|
|
7531
|
+
...(log.portions ?? []).map((p2) => p2.path)
|
|
7594
7532
|
]);
|
|
7595
7533
|
const anchor = log.templates.rootClaudeMd;
|
|
7596
7534
|
if (anchor !== void 0) paths.add(anchor.path);
|
|
@@ -7729,44 +7667,197 @@ function demotedAgentFiles(claudeDir, tracks) {
|
|
|
7729
7667
|
);
|
|
7730
7668
|
return readdirSync4(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename3(e2.name, ".md")).filter((id) => demoted.includes(id));
|
|
7731
7669
|
}
|
|
7732
|
-
function
|
|
7733
|
-
const
|
|
7734
|
-
const
|
|
7670
|
+
function updateHarnessTargets(projectDir, templatesDir, log, wants, report, foreignOwned) {
|
|
7671
|
+
const files = [];
|
|
7672
|
+
const claude = installedClis(log).includes("claude");
|
|
7673
|
+
const excluded = excludedIds(log);
|
|
7674
|
+
for (const e2 of trackOnlyFileAssets(installedTracks(projectDir))) {
|
|
7675
|
+
if (e2.target === HARNESS_ANCHOR_FILE || e2.target === SETTINGS_TARGET) continue;
|
|
7676
|
+
if (!wants(e2.target.startsWith(".claude/hooks/") ? "hooks" : "rules")) continue;
|
|
7677
|
+
if (e2.target.startsWith(".claude/") && !claude) continue;
|
|
7678
|
+
if (isBaselineExcluded(e2.target, excluded)) continue;
|
|
7679
|
+
const source = join30(templatesDir, e2.source);
|
|
7680
|
+
if (!existsSync21(source)) continue;
|
|
7681
|
+
const policy = POLICY_DIRS.find((d2) => e2.target.startsWith(`.claude/${d2.dir}/`));
|
|
7682
|
+
files.push({
|
|
7683
|
+
path: e2.target,
|
|
7684
|
+
source,
|
|
7685
|
+
unit: e2.target,
|
|
7686
|
+
unitExisted: existsSync21(join30(projectDir, e2.target)),
|
|
7687
|
+
label: policy === void 0 ? e2.target : `.claude/${policy.dir}`
|
|
7688
|
+
});
|
|
7689
|
+
}
|
|
7690
|
+
const refreshedSkills = [];
|
|
7691
|
+
const skillsSource = join30(templatesDir, "skills");
|
|
7692
|
+
if (!claude || !(wants("skills") || wants("new-skills")) || !existsSync21(skillsSource)) {
|
|
7693
|
+
return { files, refreshedSkills };
|
|
7694
|
+
}
|
|
7695
|
+
const ids = recordedSkillIds(projectDir, log);
|
|
7696
|
+
const creatable = claudeSkillIds(projectDir, log);
|
|
7697
|
+
const excludedSkill = excludedSkillOf(log);
|
|
7698
|
+
for (const d2 of readdirSync4(skillsSource, { withFileTypes: true })) {
|
|
7699
|
+
if (!d2.isDirectory()) continue;
|
|
7700
|
+
const unit = `.claude/skills/${d2.name}`;
|
|
7701
|
+
if (occupiedByNonDirectory(join30(projectDir, unit))) {
|
|
7702
|
+
report.skillsSkippedLinks.push(d2.name);
|
|
7703
|
+
continue;
|
|
7704
|
+
}
|
|
7705
|
+
if (!ids.has(d2.name) || excludedSkill(d2.name)) continue;
|
|
7706
|
+
const existed = existsSync21(join30(projectDir, unit));
|
|
7707
|
+
if (existed ? !wants("skills") : !wants("new-skills") || !creatable.has(d2.name)) continue;
|
|
7708
|
+
for (const rel of listFilesRecursive(join30(skillsSource, d2.name))) {
|
|
7709
|
+
const foreign = foreignOwnedTarget(projectDir, `${unit}/${rel}`);
|
|
7710
|
+
if (foreign !== null) {
|
|
7711
|
+
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
7712
|
+
continue;
|
|
7713
|
+
}
|
|
7714
|
+
const source = join30(skillsSource, d2.name, rel);
|
|
7715
|
+
files.push({
|
|
7716
|
+
path: `${unit}/${rel}`,
|
|
7717
|
+
source,
|
|
7718
|
+
unit,
|
|
7719
|
+
unitExisted: existed,
|
|
7720
|
+
label: ".claude/skills"
|
|
7721
|
+
});
|
|
7722
|
+
}
|
|
7723
|
+
if (existed) refreshedSkills.push(d2.name);
|
|
7724
|
+
}
|
|
7725
|
+
return { files, refreshedSkills };
|
|
7726
|
+
}
|
|
7727
|
+
function updateHarnessFiles(args) {
|
|
7728
|
+
const { projectDir, templatesDir, log, wants, outside, report } = args;
|
|
7729
|
+
const now = /* @__PURE__ */ new Date();
|
|
7730
|
+
const claude = installedClis(log).includes("claude");
|
|
7731
|
+
const claims = retiredClaims(log, projectDir, log.policyFiles ?? []);
|
|
7732
|
+
const isTarget = recordedTrackTargets(log);
|
|
7733
|
+
const recordOf = (path) => recorded(log, path, { isTarget });
|
|
7734
|
+
const writer = createInstallWriter({
|
|
7735
|
+
projectDir,
|
|
7736
|
+
previousLog: log,
|
|
7737
|
+
excluded: excludedIds(log),
|
|
7738
|
+
now,
|
|
7739
|
+
run: "update"
|
|
7740
|
+
});
|
|
7735
7741
|
const foreignOwned = [];
|
|
7736
|
-
const
|
|
7737
|
-
const
|
|
7738
|
-
const
|
|
7739
|
-
const
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
+
const targets = updateHarnessTargets(projectDir, templatesDir, log, wants, report, foreignOwned);
|
|
7743
|
+
for (const t of targets.files) writer.harness(t.path, { source: t.source }, { isTarget });
|
|
7744
|
+
const prunedPolicy = /* @__PURE__ */ new Set();
|
|
7745
|
+
for (const { dir, ext } of POLICY_DIRS) {
|
|
7746
|
+
if (!claude || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
|
|
7747
|
+
const label = `.claude/${dir}`;
|
|
7748
|
+
report.updated[label] = 0;
|
|
7749
|
+
const ctx = { prefix: dir, outside, recordOf };
|
|
7750
|
+
report.pruned[label] = pruneOrphans(
|
|
7751
|
+
join30(projectDir, label),
|
|
7752
|
+
join30(templatesDir, dir),
|
|
7753
|
+
ext,
|
|
7754
|
+
ctx,
|
|
7755
|
+
now
|
|
7756
|
+
);
|
|
7757
|
+
for (const f of report.pruned[label]) prunedPolicy.add(`${dir}/${f}`);
|
|
7758
|
+
}
|
|
7759
|
+
if (claude && wants("skills")) report.updated[".claude/skills"] = 0;
|
|
7760
|
+
const skillPrune = pruneSkillExtras(
|
|
7761
|
+
projectDir,
|
|
7762
|
+
templatesDir,
|
|
7763
|
+
targets.refreshedSkills,
|
|
7764
|
+
outside,
|
|
7765
|
+
recordOf,
|
|
7766
|
+
now
|
|
7742
7767
|
);
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7768
|
+
report.skillsPruned = skillPrune.pruned;
|
|
7769
|
+
report.skillsBackedUp.push(...skillPrune.backedUp);
|
|
7770
|
+
const restored = prunedPolicy.size === 0 ? [] : restoreDisplaced(log, projectDir, {
|
|
7771
|
+
has: (p2) => p2.startsWith(".claude/") && prunedPolicy.has(p2.slice(".claude/".length)),
|
|
7772
|
+
write: true
|
|
7773
|
+
});
|
|
7774
|
+
const ledger = writer.ledger();
|
|
7775
|
+
recordHarnessWrites(projectDir, ledger, {
|
|
7776
|
+
claims,
|
|
7777
|
+
prunedPolicy,
|
|
7778
|
+
prunedSkill: new Set(skillPrune.pruned),
|
|
7779
|
+
restored,
|
|
7780
|
+
walkedSkills: wants("skills")
|
|
7781
|
+
});
|
|
7782
|
+
const byPath = new Map(targets.files.map((t) => [t.path, t]));
|
|
7783
|
+
const created = /* @__PURE__ */ new Map();
|
|
7784
|
+
for (const w3 of ledger.writes) {
|
|
7785
|
+
const t = byPath.get(w3.path);
|
|
7786
|
+
if (t === void 0) continue;
|
|
7787
|
+
if (!t.unitExisted) {
|
|
7788
|
+
if (w3.verdict === "create")
|
|
7789
|
+
created.set(t.unit, created.get(t.unit) === true || w3.line === LINES.restored);
|
|
7752
7790
|
continue;
|
|
7753
7791
|
}
|
|
7754
|
-
if (
|
|
7755
|
-
|
|
7756
|
-
if (
|
|
7757
|
-
|
|
7758
|
-
if (
|
|
7759
|
-
|
|
7760
|
-
|
|
7792
|
+
if (w3.verdict !== "leave") report.updated[t.label] = (report.updated[t.label] ?? 0) + 1;
|
|
7793
|
+
if (w3.verdict !== "backup+overwrite" || w3.record !== "sha") continue;
|
|
7794
|
+
if (w3.path.startsWith(".claude/skills/"))
|
|
7795
|
+
report.skillsBackedUp.push(w3.path.slice(".claude/skills/".length));
|
|
7796
|
+
else if (w3.path.startsWith(".claude/"))
|
|
7797
|
+
report.policyBackedUp.push(w3.path.slice(".claude/".length));
|
|
7798
|
+
}
|
|
7799
|
+
for (const [unit, wasRestored] of created)
|
|
7800
|
+
(wasRestored ? report.restored : report.installedNew).push(unit);
|
|
7801
|
+
return {
|
|
7802
|
+
displaced: ledger.rootFiles.filter((f) => f.change === "displaced"),
|
|
7803
|
+
restored,
|
|
7804
|
+
foreignOwned,
|
|
7805
|
+
outside: ledger.outside
|
|
7806
|
+
};
|
|
7807
|
+
}
|
|
7808
|
+
function pruneSkillExtras(projectDir, templatesDir, ids, outside, recordOf, now) {
|
|
7809
|
+
const pruned = [];
|
|
7810
|
+
const backedUp = [];
|
|
7811
|
+
for (const id of ids) {
|
|
7812
|
+
const targetSkill = join30(projectDir, ".claude/skills", id);
|
|
7813
|
+
const bundled = new Set(listFilesRecursive(join30(templatesDir, "skills", id)));
|
|
7814
|
+
for (const rel of listFilesRecursive(targetSkill)) {
|
|
7815
|
+
if (bundled.has(rel) || basename3(rel).includes(".backup-")) continue;
|
|
7816
|
+
if (foreignOwnedTarget(projectDir, `.claude/skills/${id}/${rel}`) !== null) continue;
|
|
7817
|
+
const targetFile = join30(targetSkill, rel);
|
|
7818
|
+
if (outside.skip(targetFile)) continue;
|
|
7819
|
+
const rec = recordOf(`.claude/skills/${id}/${rel}`);
|
|
7820
|
+
if (rec.state !== "sha" || hashContent(readFileSync21(targetFile, "utf8")) !== rec.sha256) {
|
|
7821
|
+
backupFile(targetFile, now);
|
|
7822
|
+
backedUp.push(`${id}/${rel}`);
|
|
7823
|
+
}
|
|
7824
|
+
unlinkSync2(targetFile);
|
|
7825
|
+
pruned.push(`${id}/${rel}`);
|
|
7826
|
+
for (let d2 = dirname7(targetFile); d2 !== targetSkill && readdirSync4(d2).length === 0; d2 = dirname7(d2))
|
|
7827
|
+
rmdirSync2(d2);
|
|
7761
7828
|
}
|
|
7762
|
-
mkdirSync7(dirname7(target), { recursive: true });
|
|
7763
|
-
copyFileSync(source, target);
|
|
7764
|
-
const recordedAs = entry.target.startsWith(".claude/") ? entry.target.slice(8) : entry.target;
|
|
7765
|
-
const wired = entry.target.startsWith(".claude/hooks/") && wiredBefore.has(entry.target.slice(".claude/hooks/".length));
|
|
7766
|
-
if (priorBaseline.has(recordedAs) || wired) restored.push(entry.target);
|
|
7767
|
-
else installed.push(entry.target);
|
|
7768
7829
|
}
|
|
7769
|
-
return {
|
|
7830
|
+
return { pruned, backedUp };
|
|
7831
|
+
}
|
|
7832
|
+
function recordHarnessWrites(projectDir, ledger, x) {
|
|
7833
|
+
const log = readInstallLog(projectDir);
|
|
7834
|
+
if (!log) return;
|
|
7835
|
+
const skillLedger = log.skillFiles === void 0 && !x.walkedSkills ? [] : ledger.skillFiles;
|
|
7836
|
+
const policy = mergeExternalFiles(log.policyFiles, ledger.policyFiles).filter(
|
|
7837
|
+
(f) => !x.prunedPolicy.has(f.path)
|
|
7838
|
+
);
|
|
7839
|
+
const have = new Set(policy.map((f) => f.path));
|
|
7840
|
+
const next = {
|
|
7841
|
+
...log,
|
|
7842
|
+
policyFiles: [
|
|
7843
|
+
...policy,
|
|
7844
|
+
...x.claims.filter((c3) => !x.prunedPolicy.has(c3.path) && !have.has(c3.path))
|
|
7845
|
+
],
|
|
7846
|
+
skillFiles: mergeExternalFiles(log.skillFiles, skillLedger).filter(
|
|
7847
|
+
(f) => !x.prunedSkill.has(f.path)
|
|
7848
|
+
),
|
|
7849
|
+
externalFiles: mergeExternalFiles(log.externalFiles, ledger.externalFiles),
|
|
7850
|
+
rootFiles: withoutRestored(
|
|
7851
|
+
mergeRootFiles(log.rootFiles, withoutLostBackups(log.rootFiles, ledger.rootFiles)),
|
|
7852
|
+
x.restored
|
|
7853
|
+
)
|
|
7854
|
+
};
|
|
7855
|
+
for (const key of ["policyFiles", "skillFiles", "externalFiles", "rootFiles"])
|
|
7856
|
+
if (next[key]?.length === 0) delete next[key];
|
|
7857
|
+
try {
|
|
7858
|
+
writeInstallLog(projectDir, next);
|
|
7859
|
+
} catch {
|
|
7860
|
+
}
|
|
7770
7861
|
}
|
|
7771
7862
|
var BACKUP_SUFFIX = /\.backup-\d{8}T\d{6}(-\d+)?$/;
|
|
7772
7863
|
var BACKUP_SCAN_DIRS = [
|
|
@@ -7815,51 +7906,6 @@ function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new S
|
|
|
7815
7906
|
} catch {
|
|
7816
7907
|
}
|
|
7817
7908
|
}
|
|
7818
|
-
function installNewSkillDirs(projectDir, templatesDir, tracks, outside) {
|
|
7819
|
-
const installed = [];
|
|
7820
|
-
const restored = [];
|
|
7821
|
-
const foreignOwned = [];
|
|
7822
|
-
const log = readInstallLog(projectDir);
|
|
7823
|
-
const priorIds = new Set((log?.skillFiles ?? []).map((f) => f.path.split("/")[0] ?? ""));
|
|
7824
|
-
const excluded = excludedIds(log);
|
|
7825
|
-
const skillExcluded = excluded;
|
|
7826
|
-
if (log !== null && !installedClis(log).includes("claude"))
|
|
7827
|
-
return { installed, restored, foreignOwned };
|
|
7828
|
-
const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
|
|
7829
|
-
for (const entry of buildManifest(spec)) {
|
|
7830
|
-
if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
|
|
7831
|
-
if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
|
|
7832
|
-
if (skillExcluded.has(entry.target.slice(".claude/skills/".length))) continue;
|
|
7833
|
-
const target = join30(projectDir, entry.target);
|
|
7834
|
-
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
7835
|
-
if (foreign !== null) {
|
|
7836
|
-
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
7837
|
-
continue;
|
|
7838
|
-
}
|
|
7839
|
-
if (existsSync21(target)) continue;
|
|
7840
|
-
const source = join30(templatesDir, entry.source);
|
|
7841
|
-
if (!existsSync21(source)) continue;
|
|
7842
|
-
if (outside.skip(target)) continue;
|
|
7843
|
-
copyDir(source, target, (rel) => foreignOwnedTarget(projectDir, `${entry.target}/${rel}`));
|
|
7844
|
-
if (priorIds.has(entry.target.slice(".claude/skills/".length))) restored.push(entry.target);
|
|
7845
|
-
else installed.push(entry.target);
|
|
7846
|
-
}
|
|
7847
|
-
return { installed, restored, foreignOwned };
|
|
7848
|
-
}
|
|
7849
|
-
function recordNewSkillBaseline(projectDir, templatesDir, targets) {
|
|
7850
|
-
const log = readInstallLog(projectDir);
|
|
7851
|
-
if (!log) return;
|
|
7852
|
-
if (log.skillFiles === void 0) return;
|
|
7853
|
-
const ids = new Set(targets.map((t) => t.slice(".claude/skills/".length)));
|
|
7854
|
-
const added = collectSkillHashes(projectDir, templatesDir).filter(
|
|
7855
|
-
(f) => ids.has(f.path.split("/")[0] ?? "")
|
|
7856
|
-
);
|
|
7857
|
-
const kept = (log.skillFiles ?? []).filter((f) => !ids.has(f.path.split("/")[0] ?? ""));
|
|
7858
|
-
try {
|
|
7859
|
-
writeInstallLog(projectDir, { ...log, skillFiles: [...kept, ...added] });
|
|
7860
|
-
} catch {
|
|
7861
|
-
}
|
|
7862
|
-
}
|
|
7863
7909
|
function trackOnlyFileAssets(tracks) {
|
|
7864
7910
|
const base = { tracks, selectedInternalSkills: [] };
|
|
7865
7911
|
const optedOut = { ...base, withTauri: false };
|
|
@@ -7906,7 +7952,7 @@ function upsertRootImport(projectDir, outside) {
|
|
|
7906
7952
|
const next = upsertHarnessImport(existing, {
|
|
7907
7953
|
projectName: basename3(projectDir),
|
|
7908
7954
|
tracks: installedTracks(projectDir),
|
|
7909
|
-
// ADR-085 — update 는 선택 목록을 인자로 받지 않는다.
|
|
7955
|
+
// ADR-085 — update 는 선택 목록을 인자로 받지 않는다. 앞 단계(하네스 파일 쓰기)가 지나간 디스크로 판정한다:
|
|
7910
7956
|
// `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
|
|
7911
7957
|
continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
|
|
7912
7958
|
(id) => existsSync21(join30(projectDir, ".claude/skills", id))
|
|
@@ -7979,16 +8025,20 @@ function recordedBundledSkills(projectDir, log) {
|
|
|
7979
8025
|
return INTERNAL_BUNDLED_SKILL_IDS.filter((id) => ids.has(id));
|
|
7980
8026
|
}
|
|
7981
8027
|
function recordedSkillIds(projectDir, log) {
|
|
8028
|
+
const ids = claudeSkillIds(projectDir, log);
|
|
8029
|
+
for (const f of log.externalFiles ?? []) {
|
|
8030
|
+
const id = SHARED_SKILL_ID.exec(f.path)?.[1];
|
|
8031
|
+
if (id !== void 0) ids.add(id);
|
|
8032
|
+
}
|
|
8033
|
+
return ids;
|
|
8034
|
+
}
|
|
8035
|
+
function claudeSkillIds(projectDir, log) {
|
|
7982
8036
|
const isTarget = recordedTrackTargets(log);
|
|
7983
8037
|
const ids = new Set(bundledSkillTargets(projectDir, log));
|
|
7984
8038
|
for (const f of log.skillFiles ?? []) {
|
|
7985
8039
|
const id = f.path.split("/")[0] ?? "";
|
|
7986
8040
|
if (recorded(log, `.claude/skills/${f.path}`, { isTarget }).state !== "none") ids.add(id);
|
|
7987
8041
|
}
|
|
7988
|
-
for (const f of log.externalFiles ?? []) {
|
|
7989
|
-
const id = SHARED_SKILL_ID.exec(f.path)?.[1];
|
|
7990
|
-
if (id !== void 0) ids.add(id);
|
|
7991
|
-
}
|
|
7992
8042
|
return ids;
|
|
7993
8043
|
}
|
|
7994
8044
|
var SHARED_SKILL_ID = /^\.agents\/skills\/([^/]+)\//;
|
|
@@ -8073,7 +8123,7 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
8073
8123
|
// 기록 없음: CLI 경로로는 도달 불가 — 진입 판정(#699)이 먼저 거절. 엔진 단위 방어(빈 집합).
|
|
8074
8124
|
recordedClis: log === null ? [] : installedClis(log),
|
|
8075
8125
|
// #761 B-1 — 외부 기록이 아예 없던 옛 판의 산출물은 첫 접촉이 아니라 체크섬 없는 하네스 파일이다(install 과 같은 판정).
|
|
8076
|
-
// 앞 단계(
|
|
8126
|
+
// 앞 단계(하네스 파일 쓰기)가 헬퍼만 든 `externalFiles` 를 적어도 같은 답이다 — 헬퍼는 외부 자리로 세지 않는다(B-2)
|
|
8077
8127
|
owned: recordedOwner(log)
|
|
8078
8128
|
});
|
|
8079
8129
|
const anchorPath = result.anchorRetired?.action === "removed" ? result.anchorRetired.path : null;
|
|
@@ -8200,229 +8250,27 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
|
|
|
8200
8250
|
})
|
|
8201
8251
|
};
|
|
8202
8252
|
}
|
|
8203
|
-
function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
8204
|
-
if (!existsSync21(target) || !existsSync21(source))
|
|
8205
|
-
return { updated: 0, backedUp: [], displaced: [] };
|
|
8206
|
-
let updated = 0;
|
|
8207
|
-
const backedUp = [];
|
|
8208
|
-
const displaced = [];
|
|
8209
|
-
for (const file of readdirSync4(target)) {
|
|
8210
|
-
if (!file.endsWith(ext)) continue;
|
|
8211
|
-
const targetFile = join30(target, file);
|
|
8212
|
-
const sourceFile = join30(source, file);
|
|
8213
|
-
if (!existsSync21(sourceFile)) continue;
|
|
8214
|
-
if (ctx.outside?.skip(targetFile)) continue;
|
|
8215
|
-
const projectRel = `.claude/${ctx.prefix}/${file}`;
|
|
8216
|
-
if (ctx.excluded && isBaselineExcluded(projectRel, ctx.excluded)) continue;
|
|
8217
|
-
if (ctx.justWritten?.has(projectRel)) continue;
|
|
8218
|
-
const next = readFileSync21(sourceFile, "utf8");
|
|
8219
|
-
const rec = ctx.recordOf?.(projectRel) ?? baselineRecord(ctx.baseline, `${ctx.prefix}/${file}`);
|
|
8220
|
-
const w3 = judgedUpdateWrite(targetFile, rec, next, now);
|
|
8221
|
-
if (w3.changed) updated++;
|
|
8222
|
-
if (w3.kind === "edited") backedUp.push(`${ctx.prefix}/${file}`);
|
|
8223
|
-
const d2 = displacedEntry(w3, projectRel, targetFile);
|
|
8224
|
-
if (d2 !== null) displaced.push(d2);
|
|
8225
|
-
}
|
|
8226
|
-
return { updated, backedUp, displaced };
|
|
8227
|
-
}
|
|
8228
|
-
function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date(), foreignOf, outside, excludedSkill, opts = {}) {
|
|
8229
|
-
if (!existsSync21(targetDir) || !existsSync21(sourceDir))
|
|
8230
|
-
return {
|
|
8231
|
-
updated: 0,
|
|
8232
|
-
backedUp: [],
|
|
8233
|
-
skippedLinks: [],
|
|
8234
|
-
foreignOwned: [],
|
|
8235
|
-
pruned: [],
|
|
8236
|
-
displaced: []
|
|
8237
|
-
};
|
|
8238
|
-
let updated = 0;
|
|
8239
|
-
const backedUp = [];
|
|
8240
|
-
const skippedLinks = [];
|
|
8241
|
-
const foreignOwned = [];
|
|
8242
|
-
const pruned = [];
|
|
8243
|
-
const displaced = [];
|
|
8244
|
-
for (const skill of readdirSync4(sourceDir, { withFileTypes: true })) {
|
|
8245
|
-
if (!skill.isDirectory()) continue;
|
|
8246
|
-
const targetSkill = join30(targetDir, skill.name);
|
|
8247
|
-
if (occupiedByNonDirectory(targetSkill)) {
|
|
8248
|
-
skippedLinks.push(skill.name);
|
|
8249
|
-
continue;
|
|
8250
|
-
}
|
|
8251
|
-
if (opts.targets !== void 0 && !opts.targets.has(skill.name)) continue;
|
|
8252
|
-
if (!existsSync21(targetSkill)) continue;
|
|
8253
|
-
if (excludedSkill?.(skill.name)) continue;
|
|
8254
|
-
for (const rel of listFilesRecursive(join30(sourceDir, skill.name))) {
|
|
8255
|
-
const targetFile = join30(targetSkill, rel);
|
|
8256
|
-
const foreign = foreignOf(`${skill.name}/${rel}`);
|
|
8257
|
-
if (foreign !== null) {
|
|
8258
|
-
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
8259
|
-
continue;
|
|
8260
|
-
}
|
|
8261
|
-
if (outside?.skip(targetFile)) continue;
|
|
8262
|
-
const next = readFileSync21(join30(sourceDir, skill.name, rel), "utf8");
|
|
8263
|
-
const projectRel = `.claude/skills/${skill.name}/${rel}`;
|
|
8264
|
-
const rec = opts.recordOf?.(projectRel) ?? baselineRecord(baseline, `${skill.name}/${rel}`);
|
|
8265
|
-
const w3 = judgedUpdateWrite(targetFile, rec, next, now);
|
|
8266
|
-
if (w3.changed) updated++;
|
|
8267
|
-
if (w3.kind === "edited") backedUp.push(`${skill.name}/${rel}`);
|
|
8268
|
-
const d2 = displacedEntry(w3, projectRel, targetFile);
|
|
8269
|
-
if (d2 !== null) displaced.push(d2);
|
|
8270
|
-
}
|
|
8271
|
-
const bundled = new Set(listFilesRecursive(join30(sourceDir, skill.name)));
|
|
8272
|
-
for (const rel of listFilesRecursive(targetSkill)) {
|
|
8273
|
-
if (bundled.has(rel) || basename3(rel).includes(".backup-")) continue;
|
|
8274
|
-
if (foreignOf(`${skill.name}/${rel}`) !== null) continue;
|
|
8275
|
-
const targetFile = join30(targetSkill, rel);
|
|
8276
|
-
if (outside?.skip(targetFile)) continue;
|
|
8277
|
-
const rec = opts.recordOf?.(`.claude/skills/${skill.name}/${rel}`) ?? baselineRecord(baseline, `${skill.name}/${rel}`);
|
|
8278
|
-
if (rec.state !== "sha" || hashContent(readFileSync21(targetFile, "utf8")) !== rec.sha256) {
|
|
8279
|
-
backupFile(targetFile, now);
|
|
8280
|
-
backedUp.push(`${skill.name}/${rel}`);
|
|
8281
|
-
}
|
|
8282
|
-
unlinkSync2(targetFile);
|
|
8283
|
-
pruned.push(`${skill.name}/${rel}`);
|
|
8284
|
-
for (let d2 = dirname7(targetFile); d2 !== targetSkill && readdirSync4(d2).length === 0; d2 = dirname7(d2))
|
|
8285
|
-
rmdirSync2(d2);
|
|
8286
|
-
}
|
|
8287
|
-
}
|
|
8288
|
-
return { updated, backedUp, skippedLinks, foreignOwned, pruned, displaced };
|
|
8289
|
-
}
|
|
8290
|
-
function judgedUpdateWrite(abs, rec, next, now) {
|
|
8291
|
-
const disk = existsSync21(abs) ? readFileSync21(abs, "utf8") : null;
|
|
8292
|
-
const j4 = judge({ op: "write", kind: "harness", rec, disk, next, run: "update" });
|
|
8293
|
-
let backup;
|
|
8294
|
-
switch (j4.verdict) {
|
|
8295
|
-
case "leave":
|
|
8296
|
-
break;
|
|
8297
|
-
case "create":
|
|
8298
|
-
mkdirSync7(dirname7(abs), { recursive: true });
|
|
8299
|
-
writeFileSync7(abs, next);
|
|
8300
|
-
break;
|
|
8301
|
-
case "overwrite":
|
|
8302
|
-
writeFileSync7(abs, next);
|
|
8303
|
-
break;
|
|
8304
|
-
case "backup+overwrite":
|
|
8305
|
-
backup = backupFile(abs, now);
|
|
8306
|
-
writeFileSync7(abs, next);
|
|
8307
|
-
break;
|
|
8308
|
-
default:
|
|
8309
|
-
throw new Error(`update: '${j4.verdict}' is not a write verdict (${abs})`);
|
|
8310
|
-
}
|
|
8311
|
-
const changed = j4.verdict !== "leave";
|
|
8312
|
-
if (j4.record === "displaced") return { kind: "displacedSame", changed };
|
|
8313
|
-
if (backup === void 0) return { kind: changed ? "written" : "same", changed };
|
|
8314
|
-
return { kind: j4.record === "displaced+sha" ? "displaced" : "edited", changed, backup };
|
|
8315
|
-
}
|
|
8316
|
-
function displacedEntry(w3, projectRel, abs) {
|
|
8317
|
-
if (w3.kind === "displacedSame") return { path: projectRel, change: "displaced", notes: [] };
|
|
8318
|
-
if (w3.kind !== "displaced" || w3.backup === void 0) return null;
|
|
8319
|
-
const backupRel = `${projectRel}${w3.backup.slice(abs.length)}`;
|
|
8320
|
-
return { path: projectRel, change: "displaced", notes: [backupRel] };
|
|
8321
|
-
}
|
|
8322
|
-
function baselineRecord(baseline, key) {
|
|
8323
|
-
const sha256 = baseline.get(key);
|
|
8324
|
-
return sha256 === void 0 ? { state: "no-sha" } : { state: "sha", sha256 };
|
|
8325
|
-
}
|
|
8326
|
-
function recordedStateOf(log) {
|
|
8327
|
-
if (log === null) return void 0;
|
|
8328
|
-
const isTarget = recordedTrackTargets(log);
|
|
8329
|
-
return (projectRel) => recorded(log, projectRel, { isTarget });
|
|
8330
|
-
}
|
|
8331
|
-
function notOursUnder(displaced, prefix) {
|
|
8332
|
-
return new Set(
|
|
8333
|
-
displaced.filter((f) => f.notes.length === 0 && f.path.startsWith(prefix)).map((f) => f.path.slice(prefix.length))
|
|
8334
|
-
);
|
|
8335
|
-
}
|
|
8336
8253
|
function withoutLostBackups(prior, add) {
|
|
8337
8254
|
const backedUp = new Set(
|
|
8338
8255
|
(prior ?? []).filter((f) => f.change === "displaced" && f.notes.length > 0).map((f) => f.path)
|
|
8339
8256
|
);
|
|
8340
8257
|
return add.filter((f) => f.notes.length > 0 || !backedUp.has(f.path));
|
|
8341
8258
|
}
|
|
8342
|
-
function
|
|
8343
|
-
const log = readInstallLog(projectDir);
|
|
8344
|
-
if (!log) return;
|
|
8345
|
-
const add = withoutLostBackups(log.rootFiles, displaced);
|
|
8346
|
-
const settles = restored.some((d2) => d2.forget);
|
|
8347
|
-
if (add.length === 0 && !settles) return;
|
|
8348
|
-
const merged = add.length === 0 ? [...log.rootFiles ?? []] : mergeRootFiles(log.rootFiles, add);
|
|
8349
|
-
try {
|
|
8350
|
-
writeInstallLog(projectDir, { ...log, rootFiles: withoutRestored(merged, restored) });
|
|
8351
|
-
} catch {
|
|
8352
|
-
}
|
|
8353
|
-
}
|
|
8354
|
-
function skillBaseline(projectDir) {
|
|
8355
|
-
const log = readInstallLog(projectDir);
|
|
8356
|
-
return new Map((log?.skillFiles ?? []).map((f) => [f.path, f.sha256]));
|
|
8357
|
-
}
|
|
8358
|
-
function policyBaseline(projectDir) {
|
|
8359
|
-
const log = readInstallLog(projectDir);
|
|
8360
|
-
return new Map((log?.policyFiles ?? []).map((f) => [f.path, f.sha256]));
|
|
8361
|
-
}
|
|
8362
|
-
function refreshPolicyBaseline(projectDir, templatesDir, dirs, outside, notOurs = /* @__PURE__ */ new Set()) {
|
|
8363
|
-
const log = readInstallLog(projectDir);
|
|
8364
|
-
if (!log) return;
|
|
8365
|
-
const excluded = excludedIds(log);
|
|
8366
|
-
const inDirs = (path) => (dirs === void 0 || dirs.some((d2) => path.startsWith(`${d2}/`))) && !outside.has(path) && !isBaselineExcluded(`.claude/${path}`, excluded);
|
|
8367
|
-
const rewritten = [
|
|
8368
|
-
...(log.policyFiles ?? []).filter((f) => !inDirs(f.path)),
|
|
8369
|
-
...collectPolicyHashes(projectDir, templatesDir).filter(
|
|
8370
|
-
(f) => inDirs(f.path) && !notOurs.has(f.path)
|
|
8371
|
-
)
|
|
8372
|
-
];
|
|
8373
|
-
const policyFiles = [...rewritten, ...retiredClaims(log, projectDir, rewritten)];
|
|
8374
|
-
const next = { ...log };
|
|
8375
|
-
if (policyFiles.length > 0) next.policyFiles = policyFiles;
|
|
8376
|
-
else delete next.policyFiles;
|
|
8377
|
-
try {
|
|
8378
|
-
writeInstallLog(projectDir, next);
|
|
8379
|
-
} catch {
|
|
8380
|
-
}
|
|
8381
|
-
}
|
|
8382
|
-
function outsidePaths(outside, prefix) {
|
|
8383
|
-
return new Set(
|
|
8384
|
-
outside.list().filter((o) => o.path.startsWith(prefix)).map((o) => o.path.slice(prefix.length))
|
|
8385
|
-
);
|
|
8386
|
-
}
|
|
8387
|
-
function refreshSkillBaseline(projectDir, templatesDir, outside, targets, notOurs = /* @__PURE__ */ new Set()) {
|
|
8388
|
-
const log = readInstallLog(projectDir);
|
|
8389
|
-
if (!log) return;
|
|
8390
|
-
const excludedSkill = excludedSkillOf(log);
|
|
8391
|
-
const keepPrior = (path) => outside.has(path) || excludedSkill(path.split("/")[0] ?? "");
|
|
8392
|
-
const ours = (path) => (targets === void 0 || targets.has(path.split("/")[0] ?? "")) && !notOurs.has(path);
|
|
8393
|
-
const skillFiles = [
|
|
8394
|
-
...collectSkillHashes(projectDir, templatesDir).filter(
|
|
8395
|
-
(f) => !keepPrior(f.path) && ours(f.path)
|
|
8396
|
-
),
|
|
8397
|
-
...(log.skillFiles ?? []).filter((f) => keepPrior(f.path))
|
|
8398
|
-
];
|
|
8399
|
-
const next = { ...log };
|
|
8400
|
-
if (skillFiles.length > 0) next.skillFiles = skillFiles;
|
|
8401
|
-
else delete next.skillFiles;
|
|
8402
|
-
try {
|
|
8403
|
-
writeInstallLog(projectDir, next);
|
|
8404
|
-
} catch {
|
|
8405
|
-
}
|
|
8406
|
-
}
|
|
8407
|
-
function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
8259
|
+
function pruneOrphans(target, source, ext, ctx, now) {
|
|
8408
8260
|
if (!existsSync21(target) || !existsSync21(source)) return [];
|
|
8409
8261
|
const removed = [];
|
|
8410
8262
|
for (const file of readdirSync4(target)) {
|
|
8411
|
-
if (!file.endsWith(ext)) continue;
|
|
8412
|
-
const
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
const
|
|
8416
|
-
|
|
8417
|
-
if (ctx.outside
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
unlinkSync2(targetFile);
|
|
8423
|
-
removed.push(file);
|
|
8424
|
-
} catch {
|
|
8425
|
-
}
|
|
8263
|
+
if (!file.endsWith(ext) || existsSync21(join30(source, file))) continue;
|
|
8264
|
+
const targetFile = join30(target, file);
|
|
8265
|
+
const rec = ctx.recordOf(`.claude/${ctx.prefix}/${file}`);
|
|
8266
|
+
try {
|
|
8267
|
+
const disk = readFileSync21(targetFile, "utf8");
|
|
8268
|
+
const { verdict } = judge({ op: "remove", kind: "harness", rec, disk, next: null });
|
|
8269
|
+
if (verdict === "leave" || ctx.outside.skip(targetFile)) continue;
|
|
8270
|
+
if (verdict === "backup+remove") backupFile(targetFile, now);
|
|
8271
|
+
unlinkSync2(targetFile);
|
|
8272
|
+
removed.push(file);
|
|
8273
|
+
} catch {
|
|
8426
8274
|
}
|
|
8427
8275
|
}
|
|
8428
8276
|
return removed;
|
|
@@ -8438,53 +8286,6 @@ function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
|
8438
8286
|
return null;
|
|
8439
8287
|
}
|
|
8440
8288
|
}
|
|
8441
|
-
function refreshNeutralHelpers(projectDir, templatesDir, report) {
|
|
8442
|
-
const spec = buildAssetSpec({ tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS });
|
|
8443
|
-
const refreshed = /* @__PURE__ */ new Map();
|
|
8444
|
-
for (const entry of buildManifest(spec)) {
|
|
8445
|
-
if (entry.type !== "file" || !isCliNeutralTarget(entry.target)) continue;
|
|
8446
|
-
if (!entry.applies(spec)) continue;
|
|
8447
|
-
const abs = join30(projectDir, entry.target);
|
|
8448
|
-
const source = join30(templatesDir, entry.source);
|
|
8449
|
-
if (!existsSync21(source)) continue;
|
|
8450
|
-
let st2;
|
|
8451
|
-
try {
|
|
8452
|
-
st2 = lstatSync5(abs);
|
|
8453
|
-
} catch {
|
|
8454
|
-
continue;
|
|
8455
|
-
}
|
|
8456
|
-
if (st2.isSymbolicLink()) {
|
|
8457
|
-
report.helpersKept = [...report.helpersKept ?? [], entry.target];
|
|
8458
|
-
continue;
|
|
8459
|
-
}
|
|
8460
|
-
if (!st2.isFile()) continue;
|
|
8461
|
-
const log2 = readInstallLog(projectDir);
|
|
8462
|
-
const recorded2 = (log2?.externalFiles ?? []).find((f) => f.path === entry.target);
|
|
8463
|
-
const diskSha = hashContent(readFileSync21(abs, "utf8"));
|
|
8464
|
-
const freshContent = readFileSync21(source, "utf8");
|
|
8465
|
-
const freshSha = hashContent(freshContent);
|
|
8466
|
-
if (diskSha === freshSha) {
|
|
8467
|
-
if (recorded2?.sha256 !== freshSha) refreshed.set(entry.target, freshSha);
|
|
8468
|
-
continue;
|
|
8469
|
-
}
|
|
8470
|
-
if (recorded2 === void 0 || recorded2.sha256 !== diskSha) backupFile(abs);
|
|
8471
|
-
writeFileSync7(abs, freshContent, "utf8");
|
|
8472
|
-
refreshed.set(entry.target, freshSha);
|
|
8473
|
-
report.updated[entry.target] = (report.updated[entry.target] ?? 0) + 1;
|
|
8474
|
-
}
|
|
8475
|
-
if (refreshed.size === 0) return;
|
|
8476
|
-
const log = readInstallLog(projectDir);
|
|
8477
|
-
if (log === null) return;
|
|
8478
|
-
const next = (log.externalFiles ?? []).map(
|
|
8479
|
-
(f) => refreshed.has(f.path) ? { ...f, sha256: refreshed.get(f.path) } : f
|
|
8480
|
-
);
|
|
8481
|
-
for (const [path, sha256] of refreshed)
|
|
8482
|
-
if (!next.some((f) => f.path === path)) next.push({ path, sha256 });
|
|
8483
|
-
try {
|
|
8484
|
-
writeInstallLog(projectDir, { ...log, externalFiles: next });
|
|
8485
|
-
} catch {
|
|
8486
|
-
}
|
|
8487
|
-
}
|
|
8488
8289
|
function recordClaudeCommand(log, tracks) {
|
|
8489
8290
|
const trackArgs = (tracks.length > 0 ? tracks : log.spec.tracks).map((t) => `--track ${t}`);
|
|
8490
8291
|
return `agent-harness install ${trackArgs.join(" ")} --cli claude --scope ${log.scope}`.replace(
|
|
@@ -8532,7 +8333,7 @@ function runInstall(ctx) {
|
|
|
8532
8333
|
JSON.parse(readFileSync22(settingsPath, "utf8"));
|
|
8533
8334
|
} catch {
|
|
8534
8335
|
throw new Error(
|
|
8535
|
-
`${
|
|
8336
|
+
`${relative5(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`
|
|
8536
8337
|
);
|
|
8537
8338
|
}
|
|
8538
8339
|
}
|
|
@@ -9153,7 +8954,7 @@ function recordInterruptedInstall(ctx, error, run) {
|
|
|
9153
8954
|
const codexTrust = registeredTrust(journal.codexOptIn, ctx.projectDir);
|
|
9154
8955
|
const written = writtenSoFar(ledger, cliFiles, rootFiles, stage.rootImportWritten);
|
|
9155
8956
|
if (codexTrust) written.push(`${codexTrust.configPath} (Codex trust entry for this folder)`);
|
|
9156
|
-
const projectRel = (abs) =>
|
|
8957
|
+
const projectRel = (abs) => relative5(ctx.projectDir, abs).split(sep6).join("/");
|
|
9157
8958
|
const writtenSet = new Set(written);
|
|
9158
8959
|
const backups = [...own.backups, ...journal.backups].map((abs) => {
|
|
9159
8960
|
const original = projectRel(backupOriginal(abs) ?? abs);
|
|
@@ -9330,7 +9131,7 @@ function listHookFiles(hookDir) {
|
|
|
9330
9131
|
init_esm_shims();
|
|
9331
9132
|
import { homedir as homedir3 } from "os";
|
|
9332
9133
|
import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync24 } from "fs";
|
|
9333
|
-
import { join as join33, relative as
|
|
9134
|
+
import { join as join33, relative as relative6 } from "path";
|
|
9334
9135
|
|
|
9335
9136
|
// src/preset-recommend.ts
|
|
9336
9137
|
init_esm_shims();
|
|
@@ -10142,15 +9943,6 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
10142
9943
|
)
|
|
10143
9944
|
);
|
|
10144
9945
|
}
|
|
10145
|
-
if (baseline.updateMode.helpersKept?.length) {
|
|
10146
|
-
log(
|
|
10147
|
-
assetRow(
|
|
10148
|
-
"skip",
|
|
10149
|
-
"helper is a link",
|
|
10150
|
-
`${baseline.updateMode.helpersKept.join(", ")} \xB7 \uB9C1\uD06C\uB77C \uAC31\uC2E0\uD558\uC9C0 \uC54A\uACE0 \uB0A8\uACBC\uB2E4`
|
|
10151
|
-
)
|
|
10152
|
-
);
|
|
10153
|
-
}
|
|
10154
9946
|
for (const row of outsideLinkRows(baseline.updateMode.outsideLinks ?? [])) log(row);
|
|
10155
9947
|
if (baseline.updateMode.foreignOwned.length > 0) {
|
|
10156
9948
|
log(
|
|
@@ -10348,6 +10140,9 @@ function renderPhase1Rows(log, baseline, verbose = false) {
|
|
|
10348
10140
|
if (j4.backupAbs !== void 0) judgedBackups.add(j4.backupAbs);
|
|
10349
10141
|
log(judgedRow(j4));
|
|
10350
10142
|
}
|
|
10143
|
+
for (const f of baseline.shared ?? []) {
|
|
10144
|
+
if (f.backupAbs !== void 0 && f.removedEdited.length > 0) judgedBackups.add(f.backupAbs);
|
|
10145
|
+
}
|
|
10351
10146
|
for (const row of outsideLinkRows(baseline.outsideLinks ?? [])) log(row);
|
|
10352
10147
|
for (const row of outOfTrackRows(baseline.outOfTrack)) log(row);
|
|
10353
10148
|
for (const row of displacedRows(baseline.displacedRestored)) log(row);
|
|
@@ -10608,8 +10403,9 @@ function excludedKeyParts(f) {
|
|
|
10608
10403
|
parts.push(`removed the harness part: ${f.removedOut.join(", ")} (you asked: ${asked})`);
|
|
10609
10404
|
}
|
|
10610
10405
|
if (f.removedEdited.length > 0) {
|
|
10406
|
+
const where = f.backup === void 0 ? "went with it" : `are in the copy: ${f.backup}`;
|
|
10611
10407
|
parts.push(
|
|
10612
|
-
`your edits to ${f.removedEdited.join(", ")}
|
|
10408
|
+
`your edits to ${f.removedEdited.join(", ")} ${where} (a hook entry calls a harness script \u2014 left alone it would call a script that is no longer wired)`
|
|
10613
10409
|
);
|
|
10614
10410
|
}
|
|
10615
10411
|
if (f.keptOut.length > 0) {
|
|
@@ -10729,7 +10525,7 @@ function renderInterruptedInstall(err, e2, cwd = process.cwd()) {
|
|
|
10729
10525
|
}
|
|
10730
10526
|
const { path, error } = e2.record;
|
|
10731
10527
|
err(
|
|
10732
|
-
path !== null ? ` Already in place before it stopped \u2014 recorded in ${
|
|
10528
|
+
path !== null ? ` Already in place before it stopped \u2014 recorded in ${relative6(cwd, path) || path}:` : " Already in place before it stopped:"
|
|
10733
10529
|
);
|
|
10734
10530
|
for (const [key, n] of groups) {
|
|
10735
10531
|
err(` ${key.endsWith("/") ? `${key} (${n} file${n === 1 ? "" : "s"})` : key}`);
|
|
@@ -11192,7 +10988,7 @@ import {
|
|
|
11192
10988
|
accessSync as accessSync2,
|
|
11193
10989
|
existsSync as existsSync26,
|
|
11194
10990
|
constants as fsConstants2,
|
|
11195
|
-
lstatSync as
|
|
10991
|
+
lstatSync as lstatSync5,
|
|
11196
10992
|
readdirSync as readdirSync7,
|
|
11197
10993
|
readFileSync as readFileSync26,
|
|
11198
10994
|
readlinkSync,
|
|
@@ -11202,7 +10998,7 @@ import {
|
|
|
11202
10998
|
statSync as statSync5,
|
|
11203
10999
|
writeFileSync as writeFileSync9
|
|
11204
11000
|
} from "fs";
|
|
11205
|
-
import { dirname as dirname8, join as join35, relative as
|
|
11001
|
+
import { dirname as dirname8, join as join35, relative as relative7, resolve as resolve4, sep as sep7 } from "path";
|
|
11206
11002
|
|
|
11207
11003
|
// src/uninstall-interactive.ts
|
|
11208
11004
|
init_esm_shims();
|
|
@@ -13051,7 +12847,7 @@ function unwritablePaths(ctx) {
|
|
|
13051
12847
|
}
|
|
13052
12848
|
function safeExists(abs) {
|
|
13053
12849
|
try {
|
|
13054
|
-
|
|
12850
|
+
lstatSync5(abs);
|
|
13055
12851
|
return true;
|
|
13056
12852
|
} catch {
|
|
13057
12853
|
return false;
|
|
@@ -13127,7 +12923,17 @@ function settleLog(ctx, io) {
|
|
|
13127
12923
|
return true;
|
|
13128
12924
|
}
|
|
13129
12925
|
} else {
|
|
13130
|
-
|
|
12926
|
+
const recordDir = join35(projectDir, INSTALL_LOG_DIR);
|
|
12927
|
+
if (outsideLinkAt(projectDir, INSTALL_LOG_DIR) !== null) io.rm(recordDir);
|
|
12928
|
+
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
|
+
}
|
|
12935
|
+
pruneEmptyDirsUpward(projectDir, recordDir);
|
|
12936
|
+
}
|
|
13131
12937
|
if (outsideLinkAt(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
|
|
13132
12938
|
io.rm(legacyInstallLogPath(projectDir));
|
|
13133
12939
|
pruneEmptyDirsUpward(projectDir, join35(projectDir, LEGACY_INSTALL_LOG_DIR));
|
|
@@ -13142,7 +12948,9 @@ function dryRunLines(plan, installLog, projectDir, assetsOnly, rootFiles, harnes
|
|
|
13142
12948
|
lines2.push(c.dim(" (no project-scope assets to reverse)"));
|
|
13143
12949
|
}
|
|
13144
12950
|
lines2.push(...plan.reverseSteps.map((s) => ` \u25CB ${s.label}`));
|
|
13145
|
-
lines2.push(
|
|
12951
|
+
lines2.push(
|
|
12952
|
+
" \u25CB remove install record (.uzys-agent-harness/ \u2014 \uC124\uCE58 \uAE30\uB85D\xB7\uCC28\uB2E8 \uB85C\uADF8\xB7update \uBC31\uC5C5 \uBAA9\uB85D)"
|
|
12953
|
+
);
|
|
13146
12954
|
lines2.push(
|
|
13147
12955
|
...plan.noReversePath.flatMap((a) => [
|
|
13148
12956
|
c.dim(` \u2298 ${a.id} (${a.method}) \u2014 \uC790\uB3D9 \uB418\uB3CC\uB9AC\uAE30 \uACBD\uB85C \uC5C6\uC74C, \uAE30\uB85D \uC720\uC9C0`),
|
|
@@ -13204,7 +13012,6 @@ function backupArtifactAdvisoryLines(projectDir) {
|
|
|
13204
13012
|
}
|
|
13205
13013
|
}
|
|
13206
13014
|
for (const dir of BACKUP_SCAN_DIRS) {
|
|
13207
|
-
if (dir === INSTALL_LOG_DIR) continue;
|
|
13208
13015
|
const abs = join35(projectDir, dir);
|
|
13209
13016
|
if (!existsSync26(abs) || outsideLinkAt(projectDir, dir) !== null) continue;
|
|
13210
13017
|
for (const rel of listFilesRecursive(abs)) {
|
|
@@ -13601,15 +13408,24 @@ function sharedStripLines(results, preview) {
|
|
|
13601
13408
|
return id === null ? [] : [id.slice(id.indexOf(":") + 1)];
|
|
13602
13409
|
};
|
|
13603
13410
|
const names = (keys) => keys.flatMap(display).join(" \xB7 ");
|
|
13411
|
+
const stays = r.edited === void 0 ? " (yours stays)" : "";
|
|
13604
13412
|
if (r.fileRemoved) {
|
|
13413
|
+
const only = (verb) => r.edited === void 0 ? ` and only its part ${verb} in it` : "";
|
|
13605
13414
|
lines2.push(
|
|
13606
|
-
preview ? ` \u25CB remove ${r.path} (the harness created it
|
|
13415
|
+
preview ? ` \u25CB remove ${r.path} (the harness created it${only("is")})` : ` ${status.success(`${r.path} \u2014 removed (the harness created it${only("was")})`)}`
|
|
13607
13416
|
);
|
|
13608
13417
|
} else if (r.removed.length > 0) {
|
|
13609
13418
|
const which = r.path === AGENTS_MD2 ? "" : `: ${names(r.removed)}`;
|
|
13610
13419
|
const what = r.path === AGENTS_MD2 ? "the harness block" : "the harness part";
|
|
13611
13420
|
lines2.push(
|
|
13612
|
-
preview ? ` \u25CB remove ${what} from ${r.path}${which}
|
|
13421
|
+
preview ? ` \u25CB remove ${what} from ${r.path}${which}${stays}` : ` ${status.success(`${r.path} \u2014 removed ${what}${which}${stays}`)}`
|
|
13422
|
+
);
|
|
13423
|
+
}
|
|
13424
|
+
if (r.edited !== void 0) {
|
|
13425
|
+
const n = r.edited.keys.length;
|
|
13426
|
+
const edited = `you had edited ${n} harness hook line${n === 1 ? "" : "s"} (${names(r.edited.keys)})`;
|
|
13427
|
+
lines2.push(
|
|
13428
|
+
preview ? ` \u25CB ${r.path} \u2014 ${edited} \u2014 a copy is saved first as ${r.edited.backup}` : ` ${c.yellow("\u2298")} ${r.path} \u2014 ${edited} \u2014 your edit is in the copy: ${r.edited.backup}`
|
|
13613
13429
|
);
|
|
13614
13430
|
}
|
|
13615
13431
|
if (r.kept.length > 0) {
|
|
@@ -13802,7 +13618,10 @@ function harnessCandidates(log) {
|
|
|
13802
13618
|
const paths = [
|
|
13803
13619
|
...(log.policyFiles ?? []).map((f) => `${CLAUDE_DIR3}${f.path}`),
|
|
13804
13620
|
...(log.skillFiles ?? []).map((f) => `${SKILLS_DIR3}${f.path}`),
|
|
13805
|
-
...(log.externalFiles ?? []).map((f) => f.path)
|
|
13621
|
+
...(log.externalFiles ?? []).map((f) => f.path),
|
|
13622
|
+
// 헬퍼 — writer 기록은 `externalFiles` 에 sha 로 적지만 옛 기록(≤v26.161.0)엔 항목이 없다. `recorded()` 가 가른다
|
|
13623
|
+
// (writer 기록에 없으면 `none` = 설치자 파일 · 옛 기록이면 `no-sha` = 번들 판과 같을 때만 백업 없이)
|
|
13624
|
+
...helperSources().keys(),
|
|
13806
13625
|
// 어느 필드에도 적힌 적 없는 옛 판의 하네스 파일 — `isTarget` 이 알려 준다(A8 · install 과 같은 방식)
|
|
13807
13626
|
INSTALLED_TRACKS,
|
|
13808
13627
|
// 스캔 필드가 아예 없는 옛 로그(v26.125.0 이하)는 필드가 하네스 파일을 열거하지 못한다 — manifest 의 하네스 자리 ·
|
|
@@ -13816,11 +13635,18 @@ function harnessTargets(log) {
|
|
|
13816
13635
|
const tracked = recordedTrackTargets(log);
|
|
13817
13636
|
return (p2) => p2 === INSTALLED_TRACKS || tracked(p2);
|
|
13818
13637
|
}
|
|
13638
|
+
function helperSources() {
|
|
13639
|
+
return new Map(
|
|
13640
|
+
buildManifest({ tracks: [] }).filter((e2) => isCliNeutralTarget(e2.target)).map((e2) => [e2.target, e2.source])
|
|
13641
|
+
);
|
|
13642
|
+
}
|
|
13819
13643
|
function templateNext(harnessRoot, log, path) {
|
|
13820
13644
|
if (path === INSTALLED_TRACKS) return installedTracksText(log.spec.tracks.filter(isTrack));
|
|
13821
|
-
|
|
13645
|
+
const helper = helperSources().get(path);
|
|
13646
|
+
if (helper === void 0 && !path.startsWith(CLAUDE_DIR3)) return null;
|
|
13822
13647
|
try {
|
|
13823
|
-
|
|
13648
|
+
const source = helper ?? path.slice(CLAUDE_DIR3.length);
|
|
13649
|
+
return readFileSync26(join35(harnessRoot, "templates", source), "utf8");
|
|
13824
13650
|
} catch {
|
|
13825
13651
|
return null;
|
|
13826
13652
|
}
|
|
@@ -13846,7 +13672,7 @@ function removeRecordedFile(path, ctx) {
|
|
|
13846
13672
|
}
|
|
13847
13673
|
let st2;
|
|
13848
13674
|
try {
|
|
13849
|
-
st2 =
|
|
13675
|
+
st2 = lstatSync5(abs, { throwIfNoEntry: false });
|
|
13850
13676
|
} catch {
|
|
13851
13677
|
out.unjudged.push(path);
|
|
13852
13678
|
return;
|
|
@@ -13882,7 +13708,7 @@ function removeRecordedFile(path, ctx) {
|
|
|
13882
13708
|
if (verdict.verdict !== "remove" && verdict.verdict !== "backup+remove") return;
|
|
13883
13709
|
let backup = null;
|
|
13884
13710
|
if (verdict.verdict === "backup+remove") {
|
|
13885
|
-
backup = io.write ?
|
|
13711
|
+
backup = io.write ? relative7(projectDir, backupFile(effective)).split(sep7).join("/") : `${relative7(projectDir, effective).split(sep7).join("/")}.backup-<time>`;
|
|
13886
13712
|
}
|
|
13887
13713
|
if (io.write) {
|
|
13888
13714
|
if (effective !== abs) io.rm(abs);
|
|
@@ -13989,7 +13815,7 @@ function removeHarnessMadeShared(path, sha, projectDir, io, out, pruneFrom) {
|
|
|
13989
13815
|
out.unjudged.push(path);
|
|
13990
13816
|
return true;
|
|
13991
13817
|
}
|
|
13992
|
-
if (
|
|
13818
|
+
if (lstatSync5(abs).isSymbolicLink()) effective = real;
|
|
13993
13819
|
current = readFileSync26(effective, "utf8");
|
|
13994
13820
|
} catch {
|
|
13995
13821
|
out.unjudged.push(path);
|
|
@@ -14286,7 +14112,7 @@ function removeAnchor(log, projectDir, io) {
|
|
|
14286
14112
|
]
|
|
14287
14113
|
};
|
|
14288
14114
|
}
|
|
14289
|
-
const backup = edited ?
|
|
14115
|
+
const backup = edited ? relative7(projectDir, backupFile(abs)).split(sep7).join("/") : null;
|
|
14290
14116
|
io.rm(abs);
|
|
14291
14117
|
return {
|
|
14292
14118
|
removed: true,
|
|
@@ -14330,7 +14156,7 @@ function removeUnrecordedLegacyAgentsMd(path, projectDir, io, out, pruneFrom) {
|
|
|
14330
14156
|
try {
|
|
14331
14157
|
const real = realpathSync3(abs);
|
|
14332
14158
|
if (!statSync5(real).isFile()) return false;
|
|
14333
|
-
if (
|
|
14159
|
+
if (lstatSync5(abs).isSymbolicLink()) effective = real;
|
|
14334
14160
|
current = readFileSync26(effective, "utf8");
|
|
14335
14161
|
} catch {
|
|
14336
14162
|
return false;
|
|
@@ -14349,7 +14175,7 @@ function removeLegacyAgentsMd(path, abs, effective, strip, ctx) {
|
|
|
14349
14175
|
let backup = `${path}.backup-<time>`;
|
|
14350
14176
|
try {
|
|
14351
14177
|
if (io.write) {
|
|
14352
|
-
backup =
|
|
14178
|
+
backup = relative7(projectDir, backupFile(effective)).split(sep7).join("/");
|
|
14353
14179
|
if (strip.remainder !== null) {
|
|
14354
14180
|
writeFileSync9(effective, neutralizeScaffoldBanner(strip.remainder), "utf8");
|
|
14355
14181
|
} else {
|
|
@@ -14379,7 +14205,7 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
14379
14205
|
const abs = join35(projectDir, path);
|
|
14380
14206
|
let isLink;
|
|
14381
14207
|
try {
|
|
14382
|
-
isLink =
|
|
14208
|
+
isLink = lstatSync5(abs).isSymbolicLink();
|
|
14383
14209
|
} catch {
|
|
14384
14210
|
continue;
|
|
14385
14211
|
}
|
|
@@ -14395,7 +14221,7 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
14395
14221
|
try {
|
|
14396
14222
|
if (hashContent(readFileSync26(abs, "utf8")) !== sha256) {
|
|
14397
14223
|
const backup = backupFile(abs);
|
|
14398
|
-
notes.push(`backed up ${path} \u2192 ${
|
|
14224
|
+
notes.push(`backed up ${path} \u2192 ${relative7(projectDir, backup)} (changed since install)`);
|
|
14399
14225
|
}
|
|
14400
14226
|
rm(abs);
|
|
14401
14227
|
removed.push(path);
|
|
@@ -14410,8 +14236,8 @@ function removeToolFiles(files, projectDir, rm) {
|
|
|
14410
14236
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
14411
14237
|
let cursor = startDir;
|
|
14412
14238
|
while (true) {
|
|
14413
|
-
const rel =
|
|
14414
|
-
if (rel === "" || rel.startsWith(`..${
|
|
14239
|
+
const rel = relative7(projectDir, cursor);
|
|
14240
|
+
if (rel === "" || rel.startsWith(`..${sep7}`) || rel === "..") return;
|
|
14415
14241
|
try {
|
|
14416
14242
|
if (readdirSync7(cursor).length > 0) return;
|
|
14417
14243
|
rmdirSync3(cursor);
|