@uzysjung/agent-harness 26.132.0 → 26.133.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -712,7 +712,7 @@ var cac = (name = "") => new CAC(name);
712
712
  // package.json
713
713
  var package_default = {
714
714
  name: "@uzysjung/agent-harness",
715
- version: "26.132.0",
715
+ version: "26.133.0",
716
716
  description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
717
717
  type: "module",
718
718
  publishConfig: {
@@ -906,18 +906,18 @@ function padDisplay(s, width) {
906
906
  init_esm_shims();
907
907
  import {
908
908
  chmodSync as chmodSync2,
909
- existsSync as existsSync12,
910
- mkdirSync as mkdirSync5,
909
+ existsSync as existsSync13,
910
+ mkdirSync as mkdirSync6,
911
911
  readdirSync as readdirSync4,
912
- readFileSync as readFileSync11,
913
- writeFileSync as writeFileSync9
912
+ readFileSync as readFileSync12,
913
+ writeFileSync as writeFileSync7
914
914
  } from "fs";
915
- import { basename as basename4, dirname as dirname5, join as join11, resolve } from "path";
915
+ import { basename as basename4, dirname as dirname6, join as join11, resolve } from "path";
916
916
 
917
917
  // src/antigravity/transform.ts
918
918
  init_esm_shims();
919
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
920
- import { basename, join as join2 } from "path";
919
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
920
+ import { basename, join as join3 } from "path";
921
921
 
922
922
  // src/codex/agents-md.ts
923
923
  init_esm_shims();
@@ -960,6 +960,11 @@ function portBody(body) {
960
960
  return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
961
961
  }
962
962
 
963
+ // src/owned-write.ts
964
+ init_esm_shims();
965
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
966
+ import { dirname as dirname3, relative, sep } from "path";
967
+
963
968
  // src/fs-ops.ts
964
969
  init_esm_shims();
965
970
  import {
@@ -1050,6 +1055,218 @@ function ensureProjectSkeleton(projectDir) {
1050
1055
  }
1051
1056
  }
1052
1057
 
1058
+ // src/install-log.ts
1059
+ init_esm_shims();
1060
+ import { createHash } from "crypto";
1061
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
1062
+ import { dirname as dirname2, join as join2 } from "path";
1063
+ var INSTALL_LOG_FILENAME = ".harness-install.json";
1064
+ var INSTALL_LOG_VERSION = 1;
1065
+ var POLICY_DIRS = [
1066
+ { dir: "rules", ext: ".md" },
1067
+ { dir: "agents", ext: ".md" },
1068
+ { dir: "commands/uzys", ext: ".md" },
1069
+ { dir: "hooks", ext: ".sh" }
1070
+ ];
1071
+ function buildAssetEntries(report, scope) {
1072
+ if (!report) return [];
1073
+ return report.attempted.filter((r) => r.ok).map((r) => assetToLogEntry(r.asset, scope, r.version));
1074
+ }
1075
+ function assetToLogEntry(asset, scope, version) {
1076
+ const detail = methodDetail(asset.method);
1077
+ const entry = {
1078
+ id: asset.id,
1079
+ category: asset.category,
1080
+ method: asset.method.kind,
1081
+ scope,
1082
+ detail
1083
+ };
1084
+ if (version) entry.version = version;
1085
+ return entry;
1086
+ }
1087
+ function methodDetail(method) {
1088
+ switch (method.kind) {
1089
+ case "plugin":
1090
+ return { marketplace: method.marketplace, pluginId: method.pluginId };
1091
+ case "skill":
1092
+ return { source: method.source, ...method.skill ? { skill: method.skill } : {} };
1093
+ case "npm":
1094
+ return { pkg: method.pkg };
1095
+ case "npx-run":
1096
+ return { cmd: method.cmd, args: (method.args ?? []).join(" ") };
1097
+ case "shell-script":
1098
+ return { script: method.script, args: method.args.join(" ") };
1099
+ case "internal":
1100
+ return { key: method.key };
1101
+ }
1102
+ }
1103
+ function buildInstallLog(spec, external, scope, rootClaudeMd, previous, claudeDirMovedAside = false, rootFiles = []) {
1104
+ const templates = {
1105
+ claudeDir: ".claude/",
1106
+ ...spec.cli.includes("codex") ? { codexDir: ".codex/" } : {},
1107
+ ...spec.cli.includes("opencode") ? { opencodeDir: ".opencode/" } : {},
1108
+ ...rootClaudeMd ? { rootClaudeMd } : {}
1109
+ };
1110
+ const log = {
1111
+ schemaVersion: INSTALL_LOG_VERSION,
1112
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1113
+ scope,
1114
+ spec: {
1115
+ tracks: spec.tracks,
1116
+ cli: spec.cli
1117
+ },
1118
+ // 이번 설치가 만든 항목이 이기고, 이번에 안 만든 항목은 이전 값을 그대로 둔다.
1119
+ // (예: claude 로 깔고 나중에 codex 만 추가 설치해도 root CLAUDE.md 기록이 살아남는다)
1120
+ templates: { ...previous?.templates, ...templates },
1121
+ assets: mergeAssets(
1122
+ claudeDirMovedAside ? previous?.assets?.filter(survivesClaudeDirRename) : previous?.assets,
1123
+ buildAssetEntries(external, scope)
1124
+ )
1125
+ };
1126
+ const mergedRootFiles = mergeRootFiles(previous?.rootFiles, rootFiles);
1127
+ if (mergedRootFiles.length > 0) log.rootFiles = mergedRootFiles;
1128
+ return log;
1129
+ }
1130
+ function mergeRootFiles(previous, current) {
1131
+ const byPath = /* @__PURE__ */ new Map();
1132
+ for (const file of [...previous ?? [], ...current]) {
1133
+ const prior = byPath.get(file.path);
1134
+ byPath.set(
1135
+ file.path,
1136
+ prior ? {
1137
+ path: file.path,
1138
+ change: prior.change === "created" ? "created" : file.change,
1139
+ notes: [.../* @__PURE__ */ new Set([...prior.notes, ...file.notes])]
1140
+ } : file
1141
+ );
1142
+ }
1143
+ return [...byPath.values()];
1144
+ }
1145
+ function survivesClaudeDirRename(asset) {
1146
+ if (asset.scope === "global") return true;
1147
+ switch (asset.method) {
1148
+ case "plugin":
1149
+ return true;
1150
+ // `~/.claude/plugins/cache` — 프로젝트 밖
1151
+ case "npm":
1152
+ return true;
1153
+ // `node_modules/`
1154
+ case "skill":
1155
+ return false;
1156
+ // `npx skills add` project scope → `.claude/skills/`
1157
+ case "shell-script":
1158
+ return false;
1159
+ // ecc-prune → `.claude/local-plugins/`
1160
+ case "npx-run":
1161
+ return false;
1162
+ case "internal":
1163
+ return false;
1164
+ }
1165
+ }
1166
+ function mergeAssets(previous, current) {
1167
+ if (!previous || previous.length === 0) return [...current];
1168
+ const currentById = new Map(current.map((a) => [a.id, a]));
1169
+ const previousIds = new Set(previous.map((a) => a.id));
1170
+ return [
1171
+ ...previous.map((a) => currentById.get(a.id) ?? a),
1172
+ ...current.filter((a) => !previousIds.has(a.id))
1173
+ ];
1174
+ }
1175
+ function hashContent(content) {
1176
+ return createHash("sha256").update(content, "utf8").digest("hex");
1177
+ }
1178
+ function isHarnessOwned(baseline, key, current) {
1179
+ const recorded = baseline.get(key);
1180
+ return recorded !== void 0 && recorded === hashContent(current);
1181
+ }
1182
+ function mergeExternalFiles(projectDir, previous, current) {
1183
+ const byPath = /* @__PURE__ */ new Map();
1184
+ for (const file of [...previous ?? [], ...current]) byPath.set(file.path, file);
1185
+ return [...byPath.values()].filter((f) => existsSync2(join2(projectDir, f.path)));
1186
+ }
1187
+ function collectSkillHashes(projectDir) {
1188
+ const skillsDir = join2(projectDir, ".claude/skills");
1189
+ return listFilesRecursive(skillsDir).map((rel) => ({
1190
+ path: rel,
1191
+ sha256: hashContent(readFileSync2(join2(skillsDir, rel), "utf8"))
1192
+ }));
1193
+ }
1194
+ function collectPolicyHashes(projectDir, templatesDir) {
1195
+ const out = [];
1196
+ for (const { dir, ext } of POLICY_DIRS) {
1197
+ const targetDir = join2(projectDir, ".claude", dir);
1198
+ const sourceDir = join2(templatesDir, dir);
1199
+ for (const rel of listFilesRecursive(targetDir)) {
1200
+ if (!rel.endsWith(ext)) continue;
1201
+ if (!existsSync2(join2(sourceDir, rel))) continue;
1202
+ out.push({
1203
+ path: `${dir}/${rel}`,
1204
+ sha256: hashContent(readFileSync2(join2(targetDir, rel), "utf8"))
1205
+ });
1206
+ }
1207
+ }
1208
+ return out;
1209
+ }
1210
+ function writeInstallLog(projectDir, log) {
1211
+ const path = join2(projectDir, ".claude", INSTALL_LOG_FILENAME);
1212
+ mkdirSync2(dirname2(path), { recursive: true });
1213
+ writeFileSync(path, `${JSON.stringify(log, null, 2)}
1214
+ `, "utf8");
1215
+ return path;
1216
+ }
1217
+ function readInstallLog(projectDir) {
1218
+ const path = join2(projectDir, ".claude", INSTALL_LOG_FILENAME);
1219
+ if (!existsSync2(path)) return null;
1220
+ try {
1221
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
1222
+ if (Array.isArray(parsed.assets)) {
1223
+ parsed.assets = parsed.assets.map(
1224
+ (a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
1225
+ );
1226
+ }
1227
+ return parsed;
1228
+ } catch {
1229
+ return null;
1230
+ }
1231
+ }
1232
+ function installLogPath(projectDir) {
1233
+ return join2(projectDir, ".claude", INSTALL_LOG_FILENAME);
1234
+ }
1235
+
1236
+ // src/owned-write.ts
1237
+ function createOwnedWriter(projectDir, baseline, now = /* @__PURE__ */ new Date()) {
1238
+ const written = /* @__PURE__ */ new Map();
1239
+ const backedUp = [];
1240
+ const backupPaths = [];
1241
+ return {
1242
+ write(absPath, content) {
1243
+ const rel = relative(projectDir, absPath).split(sep).join("/");
1244
+ const digest = hashContent(content);
1245
+ if (!existsSync3(absPath)) {
1246
+ mkdirSync3(dirname3(absPath), { recursive: true });
1247
+ writeFileSync2(absPath, content);
1248
+ } else {
1249
+ const current = readFileSync3(absPath, "utf8");
1250
+ if (current !== content) {
1251
+ if (!isHarnessOwned(baseline, rel, current)) {
1252
+ backupPaths.push(backupFile(absPath, now));
1253
+ backedUp.push(rel);
1254
+ }
1255
+ writeFileSync2(absPath, content);
1256
+ }
1257
+ }
1258
+ written.set(rel, digest);
1259
+ },
1260
+ result() {
1261
+ return {
1262
+ files: [...written].map(([path, sha256]) => ({ path, sha256 })),
1263
+ backedUp: [...backedUp],
1264
+ backupPaths: [...backupPaths]
1265
+ };
1266
+ }
1267
+ };
1268
+ }
1269
+
1053
1270
  // src/project-claude-merge.ts
1054
1271
  init_esm_shims();
1055
1272
  var TRACK_DISPLAY_NAMES = {
@@ -1144,48 +1361,48 @@ function expandTracks(tracks) {
1144
1361
 
1145
1362
  // src/antigravity/transform.ts
1146
1363
  function runAntigravityTransform(params) {
1147
- const { harnessRoot, projectDir, selectedInternalSkills = [] } = params;
1148
- const rulesFile = writeRules(harnessRoot, projectDir);
1364
+ const { harnessRoot, projectDir, selectedInternalSkills = [], baseline } = params;
1365
+ const writer = createOwnedWriter(projectDir, baseline);
1366
+ const rulesFile = writeRules(harnessRoot, projectDir, writer);
1149
1367
  const skillFiles = [];
1150
1368
  for (const id of selectedInternalSkills) {
1151
- const src = join2(harnessRoot, "templates/skills", id, "SKILL.md");
1152
- if (!existsSync2(src)) {
1369
+ const src = join3(harnessRoot, "templates/skills", id, "SKILL.md");
1370
+ if (!existsSync4(src)) {
1153
1371
  continue;
1154
1372
  }
1155
- const skillDir = join2(projectDir, ".agents", "skills", id);
1156
- ensureDir(skillDir);
1157
- const target = join2(skillDir, "SKILL.md");
1158
- writeFileSync(target, renderBundledSkill(readFileSync2(src, "utf8")));
1373
+ const target = join3(projectDir, ".agents", "skills", id, "SKILL.md");
1374
+ writer.write(target, renderBundledSkill(readFileSync4(src, "utf8")));
1159
1375
  skillFiles.push(target);
1160
1376
  }
1161
- return { rulesFile, skillFiles };
1377
+ return {
1378
+ rulesFile,
1379
+ skillFiles,
1380
+ ownership: writer.result()
1381
+ };
1162
1382
  }
1163
- function writeRules(harnessRoot, projectDir) {
1164
- const claudeMdPath = join2(harnessRoot, "templates/CLAUDE.md");
1165
- const templatePath = join2(harnessRoot, "templates/antigravity/AGENTS.md.template");
1166
- if (!existsSync2(claudeMdPath) || !existsSync2(templatePath)) {
1383
+ function writeRules(harnessRoot, projectDir, writer) {
1384
+ const claudeMdPath = join3(harnessRoot, "templates/CLAUDE.md");
1385
+ const templatePath = join3(harnessRoot, "templates/antigravity/AGENTS.md.template");
1386
+ if (!existsSync4(claudeMdPath) || !existsSync4(templatePath)) {
1167
1387
  return null;
1168
1388
  }
1169
- const claudeMd = readFileSync2(claudeMdPath, "utf8");
1170
- const template = readFileSync2(templatePath, "utf8");
1171
- const rulesDir = join2(projectDir, ".agents", "rules");
1172
- ensureDir(rulesDir);
1173
- const target = join2(rulesDir, "uzys-harness.md");
1389
+ const claudeMd = readFileSync4(claudeMdPath, "utf8");
1390
+ const template = readFileSync4(templatePath, "utf8");
1391
+ const target = join3(projectDir, ".agents", "rules", "uzys-harness.md");
1174
1392
  const rulesOut = renderAgentsMd({
1175
1393
  template,
1176
1394
  claudeMd,
1177
1395
  projectName: basename(projectDir),
1178
1396
  projectContext: renderFillScaffold()
1179
1397
  });
1180
- backupFileIfChanged(target, rulesOut);
1181
- writeFileSync(target, rulesOut);
1398
+ writer.write(target, rulesOut);
1182
1399
  return target;
1183
1400
  }
1184
1401
 
1185
1402
  // src/ci-scaffold.ts
1186
1403
  init_esm_shims();
1187
- import { existsSync as existsSync3 } from "fs";
1188
- import { join as join3 } from "path";
1404
+ import { existsSync as existsSync5 } from "fs";
1405
+ import { join as join4 } from "path";
1189
1406
  var PYTHON_TRACKS = "data|csr-fastapi|full";
1190
1407
  var VARIANTS = [
1191
1408
  {
@@ -1209,12 +1426,12 @@ function installCiScaffold(ctx) {
1209
1426
  for (const v2 of VARIANTS) {
1210
1427
  if (!v2.applies(ctx.tracks)) continue;
1211
1428
  const relTarget = `.github/workflows/${v2.target}`;
1212
- const target = join3(ctx.projectDir, relTarget);
1213
- if (existsSync3(target)) {
1429
+ const target = join4(ctx.projectDir, relTarget);
1430
+ if (existsSync5(target)) {
1214
1431
  report.skippedExisting.push(relTarget);
1215
1432
  continue;
1216
1433
  }
1217
- copyFile(join3(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
1434
+ copyFile(join4(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
1218
1435
  report.written.push(relTarget);
1219
1436
  }
1220
1437
  return report;
@@ -1223,18 +1440,18 @@ function installCiScaffold(ctx) {
1223
1440
  // src/codex/opt-in.ts
1224
1441
  init_esm_shims();
1225
1442
  import { homedir } from "os";
1226
- import { join as join4 } from "path";
1443
+ import { join as join5 } from "path";
1227
1444
 
1228
1445
  // src/codex/trust-entry.ts
1229
1446
  init_esm_shims();
1230
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1231
- import { dirname as dirname2 } from "path";
1447
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1448
+ import { dirname as dirname4 } from "path";
1232
1449
  var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
1233
1450
  function registerTrustEntry(opts) {
1234
1451
  const { configPath, projectDir } = opts;
1235
1452
  try {
1236
- mkdirSync2(dirname2(configPath), { recursive: true });
1237
- const existing = existsSync4(configPath) ? readFileSync3(configPath, "utf8") : "";
1453
+ mkdirSync4(dirname4(configPath), { recursive: true });
1454
+ const existing = existsSync6(configPath) ? readFileSync5(configPath, "utf8") : "";
1238
1455
  if (hasTrustEntry(existing, projectDir)) {
1239
1456
  return { status: "already-present" };
1240
1457
  }
@@ -1242,7 +1459,7 @@ function registerTrustEntry(opts) {
1242
1459
  [projects."${projectDir}"]
1243
1460
  trust_level = "trusted"
1244
1461
  `;
1245
- writeFileSync2(configPath, existing + block);
1462
+ writeFileSync3(configPath, existing + block);
1246
1463
  return { status: "registered" };
1247
1464
  } catch (e2) {
1248
1465
  return {
@@ -1258,8 +1475,8 @@ function hasTrustEntry(configContent, projectDir) {
1258
1475
 
1259
1476
  // src/codex/opt-in.ts
1260
1477
  function runCodexOptIn(ctx) {
1261
- const codexHome = ctx.codexHome ?? join4(homedir(), ".codex");
1262
- const configPath = join4(codexHome, "config.toml");
1478
+ const codexHome = ctx.codexHome ?? join5(homedir(), ".codex");
1479
+ const configPath = join5(codexHome, "config.toml");
1263
1480
  const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
1264
1481
  return {
1265
1482
  trustEntry: {
@@ -1272,8 +1489,8 @@ function runCodexOptIn(ctx) {
1272
1489
 
1273
1490
  // src/codex/transform.ts
1274
1491
  init_esm_shims();
1275
- import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1276
- import { basename as basename2, join as join5 } from "path";
1492
+ import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
1493
+ import { basename as basename2, join as join6 } from "path";
1277
1494
 
1278
1495
  // src/codex/config-toml.ts
1279
1496
  init_esm_shims();
@@ -1341,13 +1558,14 @@ function quoteIfNeeded(name) {
1341
1558
  var HOOK_NAMES = ["session-start"];
1342
1559
  var ENV_VAR_RENAME = /CLAUDE_PROJECT_DIR/g;
1343
1560
  function runCodexTransform(params) {
1344
- const { harnessRoot, projectDir, selectedInternalSkills = [] } = params;
1345
- const claudeMd = readRequired(join5(harnessRoot, "templates/CLAUDE.md"));
1346
- const agentsTemplate = readRequired(join5(harnessRoot, "templates/codex/AGENTS.md.template"));
1347
- const configTemplate = readRequired(join5(harnessRoot, "templates/codex/config.toml.template"));
1561
+ const { harnessRoot, projectDir, selectedInternalSkills = [], baseline } = params;
1562
+ const writer = createOwnedWriter(projectDir, baseline);
1563
+ const claudeMd = readRequired(join6(harnessRoot, "templates/CLAUDE.md"));
1564
+ const agentsTemplate = readRequired(join6(harnessRoot, "templates/codex/AGENTS.md.template"));
1565
+ const configTemplate = readRequired(join6(harnessRoot, "templates/codex/config.toml.template"));
1348
1566
  const projectName = basename2(projectDir);
1349
- const mcp = readOptionalJson(join5(harnessRoot, ".mcp.json"));
1350
- const agentsMdPath = join5(projectDir, "AGENTS.md");
1567
+ const mcp = readOptionalJson(join6(harnessRoot, ".mcp.json"));
1568
+ const agentsMdPath = join6(projectDir, "AGENTS.md");
1351
1569
  ensureDir(projectDir);
1352
1570
  const agentsMdOut = renderAgentsMd({
1353
1571
  template: agentsTemplate,
@@ -1355,11 +1573,9 @@ function runCodexTransform(params) {
1355
1573
  projectName,
1356
1574
  projectContext: renderFillScaffold()
1357
1575
  });
1358
- backupFileIfChanged(agentsMdPath, agentsMdOut);
1359
- writeFileSync3(agentsMdPath, agentsMdOut);
1360
- const configTomlPath = join5(projectDir, ".codex/config.toml");
1361
- ensureDir(join5(projectDir, ".codex"));
1362
- writeFileSync3(
1576
+ writer.write(agentsMdPath, agentsMdOut);
1577
+ const configTomlPath = join6(projectDir, ".codex/config.toml");
1578
+ writer.write(
1363
1579
  configTomlPath,
1364
1580
  renderConfigToml({
1365
1581
  template: configTemplate,
@@ -1368,46 +1584,49 @@ function runCodexTransform(params) {
1368
1584
  mcp
1369
1585
  })
1370
1586
  );
1371
- const hookDir = join5(projectDir, ".codex/hooks");
1372
- ensureDir(hookDir);
1587
+ const hookDir = join6(projectDir, ".codex/hooks");
1373
1588
  const hookFiles = [];
1374
1589
  for (const hook of HOOK_NAMES) {
1375
- const src = join5(harnessRoot, "templates/hooks", `${hook}.sh`);
1376
- if (!existsSync5(src)) {
1590
+ const src = join6(harnessRoot, "templates/hooks", `${hook}.sh`);
1591
+ if (!existsSync7(src)) {
1377
1592
  continue;
1378
1593
  }
1379
- const ported = readFileSync4(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
1380
- const target = join5(hookDir, `${hook}.sh`);
1381
- writeFileSync3(target, ported);
1594
+ const ported = readFileSync6(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
1595
+ const target = join6(hookDir, `${hook}.sh`);
1596
+ writer.write(target, ported);
1382
1597
  chmodSync(target, 493);
1383
1598
  hookFiles.push(target);
1384
1599
  }
1385
1600
  const skillFiles = [];
1386
1601
  for (const id of selectedInternalSkills) {
1387
- const src = join5(harnessRoot, "templates/skills", id, "SKILL.md");
1388
- if (!existsSync5(src)) {
1602
+ const src = join6(harnessRoot, "templates/skills", id, "SKILL.md");
1603
+ if (!existsSync7(src)) {
1389
1604
  continue;
1390
1605
  }
1391
- const skillDir = join5(projectDir, ".agents", "skills", id);
1392
- ensureDir(skillDir);
1393
- const target = join5(skillDir, "SKILL.md");
1394
- writeFileSync3(target, renderBundledSkill(readFileSync4(src, "utf8")));
1606
+ const target = join6(projectDir, ".agents", "skills", id, "SKILL.md");
1607
+ writer.write(target, renderBundledSkill(readFileSync6(src, "utf8")));
1395
1608
  skillFiles.push(target);
1396
1609
  }
1397
- return { agentsMdPath, configTomlPath, hookFiles, skillFiles };
1610
+ return {
1611
+ agentsMdPath,
1612
+ configTomlPath,
1613
+ hookFiles,
1614
+ skillFiles,
1615
+ ownership: writer.result()
1616
+ };
1398
1617
  }
1399
1618
  function readRequired(path) {
1400
- if (!existsSync5(path)) {
1619
+ if (!existsSync7(path)) {
1401
1620
  throw new Error(`Codex transform: required source missing: ${path}`);
1402
1621
  }
1403
- return readFileSync4(path, "utf8");
1622
+ return readFileSync6(path, "utf8");
1404
1623
  }
1405
1624
  function readOptionalJson(path) {
1406
- if (!existsSync5(path)) {
1625
+ if (!existsSync7(path)) {
1407
1626
  return null;
1408
1627
  }
1409
1628
  try {
1410
- return JSON.parse(readFileSync4(path, "utf8"));
1629
+ return JSON.parse(readFileSync6(path, "utf8"));
1411
1630
  } catch {
1412
1631
  return null;
1413
1632
  }
@@ -1415,8 +1634,8 @@ function readOptionalJson(path) {
1415
1634
 
1416
1635
  // src/env-files.ts
1417
1636
  init_esm_shims();
1418
- import { appendFileSync, existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1419
- import { join as join6 } from "path";
1637
+ import { appendFileSync, existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
1638
+ import { join as join7 } from "path";
1420
1639
  var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
1421
1640
  # Copy to .env (gitignored) and fill in values: cp .env.example .env
1422
1641
 
@@ -1453,24 +1672,24 @@ function writeEnvExample(projectDir, tracks) {
1453
1672
  if (!tracks.some((t) => ENV_EXAMPLE_TRACKS.includes(t))) {
1454
1673
  return false;
1455
1674
  }
1456
- const path = join6(projectDir, ".env.example");
1457
- if (existsSync6(path)) {
1675
+ const path = join7(projectDir, ".env.example");
1676
+ if (existsSync8(path)) {
1458
1677
  return false;
1459
1678
  }
1460
1679
  writeFileSync4(path, ENV_EXAMPLE_BODY);
1461
1680
  return true;
1462
1681
  }
1463
1682
  function addGitignoreEnv(projectDir) {
1464
- const path = join6(projectDir, ".gitignore");
1465
- if (!existsSync6(path)) {
1683
+ const path = join7(projectDir, ".gitignore");
1684
+ if (!existsSync8(path)) {
1466
1685
  return false;
1467
1686
  }
1468
- const content = readFileSync5(path, "utf8");
1687
+ const content = readFileSync7(path, "utf8");
1469
1688
  if (GITIGNORE_ENV_PATTERN.test(content)) {
1470
1689
  return false;
1471
1690
  }
1472
- const sep = content.endsWith("\n") ? "" : "\n";
1473
- appendFileSync(path, `${sep}
1691
+ const sep2 = content.endsWith("\n") ? "" : "\n";
1692
+ appendFileSync(path, `${sep2}
1474
1693
  # Secret env (auto-added by agent-harness install)
1475
1694
  .env
1476
1695
  `);
@@ -1479,11 +1698,11 @@ function addGitignoreEnv(projectDir) {
1479
1698
  var NPX_SKILLS_AGENT_DIRS = [".factory/", ".goose/"];
1480
1699
  var GITIGNORE_NPX_SKILLS_HEADER = "# npx skills add multi-CLI cache (auto-added by agent-harness)";
1481
1700
  function addGitignoreNpxSkillsAgents(projectDir) {
1482
- const path = join6(projectDir, ".gitignore");
1483
- if (!existsSync6(path)) {
1701
+ const path = join7(projectDir, ".gitignore");
1702
+ if (!existsSync8(path)) {
1484
1703
  return [];
1485
1704
  }
1486
- const content = readFileSync5(path, "utf8");
1705
+ const content = readFileSync7(path, "utf8");
1487
1706
  const missing = NPX_SKILLS_AGENT_DIRS.filter((pattern) => {
1488
1707
  const lineRegex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\//g, "/")}\\s*$`, "m");
1489
1708
  return !lineRegex.test(content);
@@ -1491,25 +1710,25 @@ function addGitignoreNpxSkillsAgents(projectDir) {
1491
1710
  if (missing.length === 0) {
1492
1711
  return [];
1493
1712
  }
1494
- const sep = content.endsWith("\n") ? "" : "\n";
1713
+ const sep2 = content.endsWith("\n") ? "" : "\n";
1495
1714
  const block = [GITIGNORE_NPX_SKILLS_HEADER, ...missing].join("\n");
1496
- appendFileSync(path, `${sep}
1715
+ appendFileSync(path, `${sep2}
1497
1716
  ${block}
1498
1717
  `);
1499
1718
  return [...missing];
1500
1719
  }
1501
1720
  function writeMcpAllowlist(projectDir) {
1502
- const allowlistPath = join6(projectDir, ".mcp-allowlist");
1503
- if (existsSync6(allowlistPath)) {
1721
+ const allowlistPath = join7(projectDir, ".mcp-allowlist");
1722
+ if (existsSync8(allowlistPath)) {
1504
1723
  return null;
1505
1724
  }
1506
- const mcpPath = join6(projectDir, ".mcp.json");
1507
- if (!existsSync6(mcpPath)) {
1725
+ const mcpPath = join7(projectDir, ".mcp.json");
1726
+ if (!existsSync8(mcpPath)) {
1508
1727
  return null;
1509
1728
  }
1510
1729
  let names = [];
1511
1730
  try {
1512
- const parsed = JSON.parse(readFileSync5(mcpPath, "utf8"));
1731
+ const parsed = JSON.parse(readFileSync7(mcpPath, "utf8"));
1513
1732
  names = Object.keys(parsed.mcpServers ?? {}).sort();
1514
1733
  } catch {
1515
1734
  return null;
@@ -1532,9 +1751,9 @@ function writeMcpAllowlist(projectDir) {
1532
1751
  // src/external-installer.ts
1533
1752
  init_esm_shims();
1534
1753
  import { spawnSync } from "child_process";
1535
- import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
1754
+ import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
1536
1755
  import { homedir as homedir2 } from "os";
1537
- import { join as join7 } from "path";
1756
+ import { join as join8 } from "path";
1538
1757
  var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
1539
1758
  function selectExternalTargets(assets, ctx) {
1540
1759
  const conditionPassed = filterApplicableAssets(assets, ctx).filter(
@@ -1610,8 +1829,8 @@ function installOne(asset, ctx) {
1610
1829
  cwd
1611
1830
  );
1612
1831
  case "shell-script": {
1613
- const scriptPath = join7(ctx.harnessRoot, method.script);
1614
- if (!existsSync7(scriptPath)) {
1832
+ const scriptPath = join8(ctx.harnessRoot, method.script);
1833
+ if (!existsSync9(scriptPath)) {
1615
1834
  return {
1616
1835
  asset,
1617
1836
  ok: false,
@@ -1701,17 +1920,17 @@ function detectVersion(method, spawn) {
1701
1920
  if (at2 <= 0) return void 0;
1702
1921
  const plugin = method.pluginId.slice(0, at2);
1703
1922
  const marketplaceShort = method.pluginId.slice(at2 + 1);
1704
- const cacheBase = join7(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
1705
- if (!existsSync7(cacheBase)) return void 0;
1923
+ const cacheBase = join8(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
1924
+ if (!existsSync9(cacheBase)) return void 0;
1706
1925
  const versions = readdirSync2(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
1707
1926
  return versions.at(-1);
1708
1927
  }
1709
1928
  case "npm": {
1710
1929
  const npmRoot = getNpmGlobalRoot(spawn);
1711
1930
  if (!npmRoot) return void 0;
1712
- const pkgJson = join7(npmRoot, method.pkg, "package.json");
1713
- if (!existsSync7(pkgJson)) return void 0;
1714
- const parsed = JSON.parse(readFileSync6(pkgJson, "utf8"));
1931
+ const pkgJson = join8(npmRoot, method.pkg, "package.json");
1932
+ if (!existsSync9(pkgJson)) return void 0;
1933
+ const parsed = JSON.parse(readFileSync8(pkgJson, "utf8"));
1715
1934
  return parsed.version;
1716
1935
  }
1717
1936
  default:
@@ -1736,178 +1955,9 @@ function getNpmGlobalRoot(spawn) {
1736
1955
  return void 0;
1737
1956
  }
1738
1957
 
1739
- // src/install-log.ts
1740
- init_esm_shims();
1741
- import { createHash } from "crypto";
1742
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1743
- import { dirname as dirname3, join as join8 } from "path";
1744
- var INSTALL_LOG_FILENAME = ".harness-install.json";
1745
- var INSTALL_LOG_VERSION = 1;
1746
- var POLICY_DIRS = [
1747
- { dir: "rules", ext: ".md" },
1748
- { dir: "agents", ext: ".md" },
1749
- { dir: "commands/uzys", ext: ".md" },
1750
- { dir: "hooks", ext: ".sh" }
1751
- ];
1752
- function buildAssetEntries(report, scope) {
1753
- if (!report) return [];
1754
- return report.attempted.filter((r) => r.ok).map((r) => assetToLogEntry(r.asset, scope, r.version));
1755
- }
1756
- function assetToLogEntry(asset, scope, version) {
1757
- const detail = methodDetail(asset.method);
1758
- const entry = {
1759
- id: asset.id,
1760
- category: asset.category,
1761
- method: asset.method.kind,
1762
- scope,
1763
- detail
1764
- };
1765
- if (version) entry.version = version;
1766
- return entry;
1767
- }
1768
- function methodDetail(method) {
1769
- switch (method.kind) {
1770
- case "plugin":
1771
- return { marketplace: method.marketplace, pluginId: method.pluginId };
1772
- case "skill":
1773
- return { source: method.source, ...method.skill ? { skill: method.skill } : {} };
1774
- case "npm":
1775
- return { pkg: method.pkg };
1776
- case "npx-run":
1777
- return { cmd: method.cmd, args: (method.args ?? []).join(" ") };
1778
- case "shell-script":
1779
- return { script: method.script, args: method.args.join(" ") };
1780
- case "internal":
1781
- return { key: method.key };
1782
- }
1783
- }
1784
- function buildInstallLog(spec, external, scope, rootClaudeMd, previous, claudeDirMovedAside = false, rootFiles = []) {
1785
- const templates = {
1786
- claudeDir: ".claude/",
1787
- ...spec.cli.includes("codex") ? { codexDir: ".codex/" } : {},
1788
- ...spec.cli.includes("opencode") ? { opencodeDir: ".opencode/" } : {},
1789
- ...rootClaudeMd ? { rootClaudeMd } : {}
1790
- };
1791
- const log = {
1792
- schemaVersion: INSTALL_LOG_VERSION,
1793
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1794
- scope,
1795
- spec: {
1796
- tracks: spec.tracks,
1797
- cli: spec.cli
1798
- },
1799
- // 이번 설치가 만든 항목이 이기고, 이번에 안 만든 항목은 이전 값을 그대로 둔다.
1800
- // (예: claude 로 깔고 나중에 codex 만 추가 설치해도 root CLAUDE.md 기록이 살아남는다)
1801
- templates: { ...previous?.templates, ...templates },
1802
- assets: mergeAssets(
1803
- claudeDirMovedAside ? previous?.assets?.filter(survivesClaudeDirRename) : previous?.assets,
1804
- buildAssetEntries(external, scope)
1805
- )
1806
- };
1807
- const mergedRootFiles = mergeRootFiles(previous?.rootFiles, rootFiles);
1808
- if (mergedRootFiles.length > 0) log.rootFiles = mergedRootFiles;
1809
- return log;
1810
- }
1811
- function mergeRootFiles(previous, current) {
1812
- const byPath = /* @__PURE__ */ new Map();
1813
- for (const file of [...previous ?? [], ...current]) {
1814
- const prior = byPath.get(file.path);
1815
- byPath.set(
1816
- file.path,
1817
- prior ? {
1818
- path: file.path,
1819
- change: prior.change === "created" ? "created" : file.change,
1820
- notes: [.../* @__PURE__ */ new Set([...prior.notes, ...file.notes])]
1821
- } : file
1822
- );
1823
- }
1824
- return [...byPath.values()];
1825
- }
1826
- function survivesClaudeDirRename(asset) {
1827
- if (asset.scope === "global") return true;
1828
- switch (asset.method) {
1829
- case "plugin":
1830
- return true;
1831
- // `~/.claude/plugins/cache` — 프로젝트 밖
1832
- case "npm":
1833
- return true;
1834
- // `node_modules/`
1835
- case "skill":
1836
- return false;
1837
- // `npx skills add` project scope → `.claude/skills/`
1838
- case "shell-script":
1839
- return false;
1840
- // ecc-prune → `.claude/local-plugins/`
1841
- case "npx-run":
1842
- return false;
1843
- case "internal":
1844
- return false;
1845
- }
1846
- }
1847
- function mergeAssets(previous, current) {
1848
- if (!previous || previous.length === 0) return [...current];
1849
- const currentById = new Map(current.map((a) => [a.id, a]));
1850
- const previousIds = new Set(previous.map((a) => a.id));
1851
- return [
1852
- ...previous.map((a) => currentById.get(a.id) ?? a),
1853
- ...current.filter((a) => !previousIds.has(a.id))
1854
- ];
1855
- }
1856
- function hashContent(content) {
1857
- return createHash("sha256").update(content, "utf8").digest("hex");
1858
- }
1859
- function collectSkillHashes(projectDir) {
1860
- const skillsDir = join8(projectDir, ".claude/skills");
1861
- return listFilesRecursive(skillsDir).map((rel) => ({
1862
- path: rel,
1863
- sha256: hashContent(readFileSync7(join8(skillsDir, rel), "utf8"))
1864
- }));
1865
- }
1866
- function collectPolicyHashes(projectDir, templatesDir) {
1867
- const out = [];
1868
- for (const { dir, ext } of POLICY_DIRS) {
1869
- const targetDir = join8(projectDir, ".claude", dir);
1870
- const sourceDir = join8(templatesDir, dir);
1871
- for (const rel of listFilesRecursive(targetDir)) {
1872
- if (!rel.endsWith(ext)) continue;
1873
- if (!existsSync8(join8(sourceDir, rel))) continue;
1874
- out.push({
1875
- path: `${dir}/${rel}`,
1876
- sha256: hashContent(readFileSync7(join8(targetDir, rel), "utf8"))
1877
- });
1878
- }
1879
- }
1880
- return out;
1881
- }
1882
- function writeInstallLog(projectDir, log) {
1883
- const path = join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
1884
- mkdirSync3(dirname3(path), { recursive: true });
1885
- writeFileSync5(path, `${JSON.stringify(log, null, 2)}
1886
- `, "utf8");
1887
- return path;
1888
- }
1889
- function readInstallLog(projectDir) {
1890
- const path = join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
1891
- if (!existsSync8(path)) return null;
1892
- try {
1893
- const parsed = JSON.parse(readFileSync7(path, "utf8"));
1894
- if (Array.isArray(parsed.assets)) {
1895
- parsed.assets = parsed.assets.map(
1896
- (a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
1897
- );
1898
- }
1899
- return parsed;
1900
- } catch {
1901
- return null;
1902
- }
1903
- }
1904
- function installLogPath(projectDir) {
1905
- return join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
1906
- }
1907
-
1908
1958
  // src/mcp-merge.ts
1909
1959
  init_esm_shims();
1910
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
1960
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
1911
1961
  function parseTrackMcpMap(raw) {
1912
1962
  const rows = [];
1913
1963
  for (const line of raw.split(/\r?\n/)) {
@@ -1958,15 +2008,15 @@ function mergeMcpServers(base, rows, tracks) {
1958
2008
  return out;
1959
2009
  }
1960
2010
  function composeMcpJson(opts) {
1961
- const base = JSON.parse(readFileSync8(opts.templateMcpPath, "utf8"));
1962
- const merged = opts.existingPath && existsSync9(opts.existingPath) ? mergeUserBase(base, opts.existingPath) : base;
1963
- const mapRaw = existsSync9(opts.trackMapPath) ? readFileSync8(opts.trackMapPath, "utf8") : "";
2011
+ const base = JSON.parse(readFileSync9(opts.templateMcpPath, "utf8"));
2012
+ const merged = opts.existingPath && existsSync10(opts.existingPath) ? mergeUserBase(base, opts.existingPath) : base;
2013
+ const mapRaw = existsSync10(opts.trackMapPath) ? readFileSync9(opts.trackMapPath, "utf8") : "";
1964
2014
  const rows = parseTrackMcpMap(mapRaw);
1965
2015
  return mergeMcpServers(merged, rows, opts.tracks);
1966
2016
  }
1967
2017
  function mergeUserBase(base, existingPath) {
1968
2018
  try {
1969
- const existing = JSON.parse(readFileSync8(existingPath, "utf8"));
2019
+ const existing = JSON.parse(readFileSync9(existingPath, "utf8"));
1970
2020
  return {
1971
2021
  ...base,
1972
2022
  mcpServers: { ...base.mcpServers, ...existing.mcpServers }
@@ -1976,13 +2026,13 @@ function mergeUserBase(base, existingPath) {
1976
2026
  }
1977
2027
  }
1978
2028
  function writeMcpJson(path, mcp) {
1979
- writeFileSync6(path, `${JSON.stringify(mcp, null, 2)}
2029
+ writeFileSync5(path, `${JSON.stringify(mcp, null, 2)}
1980
2030
  `);
1981
2031
  }
1982
2032
 
1983
2033
  // src/opencode/transform.ts
1984
2034
  init_esm_shims();
1985
- import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
2035
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
1986
2036
  import { basename as basename3, join as join9 } from "path";
1987
2037
 
1988
2038
  // src/opencode/agents-md.ts
@@ -2083,7 +2133,8 @@ function parseTemplate(template) {
2083
2133
 
2084
2134
  // src/opencode/transform.ts
2085
2135
  function runOpencodeTransform(params) {
2086
- const { harnessRoot, projectDir, selectedInternalSkills = [] } = params;
2136
+ const { harnessRoot, projectDir, selectedInternalSkills = [], baseline } = params;
2137
+ const writer = createOwnedWriter(projectDir, baseline);
2087
2138
  const claudeMd = readRequired2(join9(harnessRoot, "templates/CLAUDE.md"));
2088
2139
  const agentsTemplate = readRequired2(join9(harnessRoot, "templates/opencode/AGENTS.md.template"));
2089
2140
  const opencodeTemplate = readRequired2(
@@ -2099,40 +2150,41 @@ function runOpencodeTransform(params) {
2099
2150
  projectName,
2100
2151
  projectContext: renderFillScaffold()
2101
2152
  });
2102
- backupFileIfChanged(agentsMdPath, agentsMdOut);
2103
- writeFileSync7(agentsMdPath, agentsMdOut);
2153
+ writer.write(agentsMdPath, agentsMdOut);
2104
2154
  const opencodeJsonPath = join9(projectDir, "opencode.json");
2105
- writeFileSync7(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
2155
+ writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
2106
2156
  const cmdDir = join9(projectDir, ".opencode/commands");
2107
2157
  ensureDir(cmdDir);
2108
2158
  const commandFiles = [];
2109
2159
  for (const id of selectedInternalSkills) {
2110
2160
  const src = join9(harnessRoot, "templates/skills", id, "SKILL.md");
2111
- if (!existsSync10(src)) {
2161
+ if (!existsSync11(src)) {
2112
2162
  continue;
2113
2163
  }
2114
2164
  const target = join9(cmdDir, `${id}.md`);
2115
- const shellDependent = existsSync10(join9(harnessRoot, "templates/skills", id, "scripts"));
2116
- writeFileSync7(
2117
- target,
2118
- renderCommandFromSkill(readFileSync9(src, "utf8"), id, { shellDependent })
2119
- );
2165
+ const shellDependent = existsSync11(join9(harnessRoot, "templates/skills", id, "scripts"));
2166
+ writer.write(target, renderCommandFromSkill(readFileSync10(src, "utf8"), id, { shellDependent }));
2120
2167
  commandFiles.push(target);
2121
2168
  }
2122
- return { agentsMdPath, opencodeJsonPath, commandFiles };
2169
+ return {
2170
+ agentsMdPath,
2171
+ opencodeJsonPath,
2172
+ commandFiles,
2173
+ ownership: writer.result()
2174
+ };
2123
2175
  }
2124
2176
  function readRequired2(path) {
2125
- if (!existsSync10(path)) {
2177
+ if (!existsSync11(path)) {
2126
2178
  throw new Error(`OpenCode transform: required source missing: ${path}`);
2127
2179
  }
2128
- return readFileSync9(path, "utf8");
2180
+ return readFileSync10(path, "utf8");
2129
2181
  }
2130
2182
  function readOptionalJson2(path) {
2131
- if (!existsSync10(path)) {
2183
+ if (!existsSync11(path)) {
2132
2184
  return null;
2133
2185
  }
2134
2186
  try {
2135
- return JSON.parse(readFileSync9(path, "utf8"));
2187
+ return JSON.parse(readFileSync10(path, "utf8"));
2136
2188
  } catch {
2137
2189
  return null;
2138
2190
  }
@@ -2166,14 +2218,14 @@ function addPreToolUseHook(settings, matcher, command) {
2166
2218
  init_esm_shims();
2167
2219
  import {
2168
2220
  copyFileSync as copyFileSync2,
2169
- existsSync as existsSync11,
2170
- mkdirSync as mkdirSync4,
2221
+ existsSync as existsSync12,
2222
+ mkdirSync as mkdirSync5,
2171
2223
  readdirSync as readdirSync3,
2172
- readFileSync as readFileSync10,
2224
+ readFileSync as readFileSync11,
2173
2225
  unlinkSync,
2174
- writeFileSync as writeFileSync8
2226
+ writeFileSync as writeFileSync6
2175
2227
  } from "fs";
2176
- import { dirname as dirname4, join as join10 } from "path";
2228
+ import { dirname as dirname5, join as join10 } from "path";
2177
2229
  function buildUpdateSpec(projectDir, tracks) {
2178
2230
  return {
2179
2231
  tracks: [...tracks],
@@ -2214,66 +2266,66 @@ function runUpdateMode(projectDir, templatesDir) {
2214
2266
  refreshSkillBaseline(projectDir);
2215
2267
  const claudeMd = join10(claudeDir, "CLAUDE.md");
2216
2268
  const templateMd = join10(templatesDir, "CLAUDE.md");
2217
- if (existsSync11(claudeMd) && existsSync11(templateMd)) {
2269
+ if (existsSync12(claudeMd) && existsSync12(templateMd)) {
2218
2270
  copyFileSync2(templateMd, claudeMd);
2219
2271
  report.claudeMdUpdated = true;
2220
2272
  }
2221
2273
  const settingsPath = join10(claudeDir, "settings.json");
2222
- if (existsSync11(settingsPath)) {
2274
+ if (existsSync12(settingsPath)) {
2223
2275
  report.staleHookRefs = cleanStaleHookRefs(settingsPath, join10(claudeDir, "hooks"));
2224
2276
  }
2225
2277
  return report;
2226
2278
  }
2227
- function isHarnessOwned(ctx, rel, current) {
2279
+ function isHarnessOwned2(ctx, rel, current) {
2228
2280
  const recorded = ctx.baseline.get(`${ctx.prefix}/${rel}`);
2229
2281
  return recorded !== void 0 && recorded === hashContent(current);
2230
2282
  }
2231
2283
  function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
2232
- if (!existsSync11(target) || !existsSync11(source)) return { updated: 0, backedUp: [] };
2284
+ if (!existsSync12(target) || !existsSync12(source)) return { updated: 0, backedUp: [] };
2233
2285
  let updated = 0;
2234
2286
  const backedUp = [];
2235
2287
  for (const file of readdirSync3(target)) {
2236
2288
  if (!file.endsWith(ext)) continue;
2237
2289
  const targetFile = join10(target, file);
2238
2290
  const sourceFile = join10(source, file);
2239
- if (!existsSync11(sourceFile)) continue;
2240
- const next = readFileSync10(sourceFile, "utf8");
2241
- const current = readFileSync10(targetFile, "utf8");
2291
+ if (!existsSync12(sourceFile)) continue;
2292
+ const next = readFileSync11(sourceFile, "utf8");
2293
+ const current = readFileSync11(targetFile, "utf8");
2242
2294
  if (current === next) continue;
2243
- if (!isHarnessOwned(ctx, file, current)) {
2295
+ if (!isHarnessOwned2(ctx, file, current)) {
2244
2296
  backupFile(targetFile, now);
2245
2297
  backedUp.push(`${ctx.prefix}/${file}`);
2246
2298
  }
2247
- writeFileSync8(targetFile, next);
2299
+ writeFileSync6(targetFile, next);
2248
2300
  updated++;
2249
2301
  }
2250
2302
  return { updated, backedUp };
2251
2303
  }
2252
2304
  function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date()) {
2253
- if (!existsSync11(targetDir) || !existsSync11(sourceDir)) return { updated: 0, backedUp: [] };
2305
+ if (!existsSync12(targetDir) || !existsSync12(sourceDir)) return { updated: 0, backedUp: [] };
2254
2306
  let updated = 0;
2255
2307
  const backedUp = [];
2256
2308
  for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
2257
2309
  if (!skill.isDirectory()) continue;
2258
2310
  const targetSkill = join10(targetDir, skill.name);
2259
- if (!existsSync11(targetSkill)) continue;
2311
+ if (!existsSync12(targetSkill)) continue;
2260
2312
  for (const rel of listFilesRecursive(join10(sourceDir, skill.name))) {
2261
2313
  const targetFile = join10(targetSkill, rel);
2262
- const next = readFileSync10(join10(sourceDir, skill.name, rel), "utf8");
2263
- if (!existsSync11(targetFile)) {
2264
- mkdirSync4(dirname4(targetFile), { recursive: true });
2265
- writeFileSync8(targetFile, next);
2314
+ const next = readFileSync11(join10(sourceDir, skill.name, rel), "utf8");
2315
+ if (!existsSync12(targetFile)) {
2316
+ mkdirSync5(dirname5(targetFile), { recursive: true });
2317
+ writeFileSync6(targetFile, next);
2266
2318
  updated++;
2267
2319
  continue;
2268
2320
  }
2269
- const current = readFileSync10(targetFile, "utf8");
2321
+ const current = readFileSync11(targetFile, "utf8");
2270
2322
  if (current === next) continue;
2271
2323
  const recorded = baseline.get(`${skill.name}/${rel}`);
2272
2324
  if (recorded === void 0 || hashContent(current) !== recorded) {
2273
2325
  backupFile(targetFile, now);
2274
2326
  backedUp.push(`${skill.name}/${rel}`);
2275
2327
  }
2276
- writeFileSync8(targetFile, next);
2328
+ writeFileSync6(targetFile, next);
2277
2329
  updated++;
2278
2330
  }
2279
2331
  }
@@ -2312,17 +2364,17 @@ function refreshSkillBaseline(projectDir) {
2312
2364
  }
2313
2365
  }
2314
2366
  function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
2315
- if (!existsSync11(target) || !existsSync11(source)) return [];
2367
+ if (!existsSync12(target) || !existsSync12(source)) return [];
2316
2368
  const removed = [];
2317
2369
  for (const file of readdirSync3(target)) {
2318
2370
  if (!file.endsWith(ext)) continue;
2319
2371
  const sourceFile = join10(source, file);
2320
- if (!existsSync11(sourceFile)) {
2372
+ if (!existsSync12(sourceFile)) {
2321
2373
  const targetFile = join10(target, file);
2322
2374
  if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
2323
2375
  try {
2324
- const current = readFileSync10(targetFile, "utf8");
2325
- if (!isHarnessOwned(ctx, file, current)) backupFile(targetFile, now);
2376
+ const current = readFileSync11(targetFile, "utf8");
2377
+ if (!isHarnessOwned2(ctx, file, current)) backupFile(targetFile, now);
2326
2378
  unlinkSync(targetFile);
2327
2379
  removed.push(file);
2328
2380
  } catch {
@@ -2334,7 +2386,7 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
2334
2386
  function cleanStaleHookRefs(settingsPath, hooksDir) {
2335
2387
  let settings;
2336
2388
  try {
2337
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
2389
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
2338
2390
  } catch {
2339
2391
  return [];
2340
2392
  }
@@ -2353,7 +2405,7 @@ function cleanStaleHookRefs(settingsPath, hooksDir) {
2353
2405
  }
2354
2406
  if (removed.length > 0) {
2355
2407
  const next = { ...settings, hooks: cleanedHooks };
2356
- writeFileSync8(settingsPath, `${JSON.stringify(next, null, 2)}
2408
+ writeFileSync6(settingsPath, `${JSON.stringify(next, null, 2)}
2357
2409
  `);
2358
2410
  }
2359
2411
  return removed;
@@ -2362,7 +2414,7 @@ function keepHookRef(hook, hooksDir, removed) {
2362
2414
  const refMatch = (hook?.command ?? "").match(/\/\.claude\/hooks\/([^"\s/]+\.sh)/);
2363
2415
  if (!refMatch?.[1]) return true;
2364
2416
  const fname = refMatch[1];
2365
- const exists = existsSync11(join10(hooksDir, fname));
2417
+ const exists = existsSync12(join10(hooksDir, fname));
2366
2418
  if (!exists && !removed.includes(fname)) removed.push(fname);
2367
2419
  return exists;
2368
2420
  }
@@ -2375,11 +2427,11 @@ function runInstall(ctx) {
2375
2427
  const { harnessRoot, projectDir, spec } = ctx;
2376
2428
  const mode = ctx.mode ?? "fresh";
2377
2429
  const templatesDir = join11(harnessRoot, "templates");
2378
- if (!existsSync12(templatesDir)) {
2430
+ if (!existsSync13(templatesDir)) {
2379
2431
  throw new Error(`Templates dir not found: ${templatesDir}`);
2380
2432
  }
2381
2433
  const claudeDir = join11(projectDir, ".claude");
2382
- if (mode === "update" && !existsSync12(claudeDir)) {
2434
+ if (mode === "update" && !existsSync13(claudeDir)) {
2383
2435
  throw new Error(`Update mode requires existing .claude/ at ${claudeDir}`);
2384
2436
  }
2385
2437
  const previousLog = readInstallLog(projectDir);
@@ -2396,6 +2448,13 @@ function runInstall(ctx) {
2396
2448
  options: spec.options,
2397
2449
  ...spec.userOverride ? { userOverride: spec.userOverride } : {}
2398
2450
  }) ? installCiScaffold({ harnessRoot, projectDir, tracks: spec.tracks }) : null;
2451
+ const { externalFiles, externalBackups, ...cliTransforms } = runCliTransforms(
2452
+ spec,
2453
+ harnessRoot,
2454
+ projectDir,
2455
+ manifestSpec.selectedInternalSkills,
2456
+ previousLog?.externalFiles ?? []
2457
+ );
2399
2458
  const baseline = {
2400
2459
  filesCopied: base.filesCopied,
2401
2460
  dirsCopied: base.dirsCopied,
@@ -2403,20 +2462,23 @@ function runInstall(ctx) {
2403
2462
  backup: backupPath,
2404
2463
  installedTracks: [...spec.tracks].sort(),
2405
2464
  mcpServers: Object.keys(mcpResult.mcpServers).sort(),
2406
- ...runCliTransforms(spec, harnessRoot, projectDir, manifestSpec.selectedInternalSkills),
2465
+ ...cliTransforms,
2407
2466
  ciScaffold,
2408
2467
  updateMode: null,
2409
2468
  mode,
2410
2469
  envFiles: writeEnvironmentFiles(projectDir, spec.tracks),
2411
2470
  categories: base.categories,
2412
2471
  rootClaudeMd: base.rootClaudeMd,
2413
- backups: base.backups
2472
+ // 외부 CLI 백업도 같은 줄에 노출한다 — 백업이 화면에 안 보이면 사용자는 자기 편집분이
2473
+ // 어디 갔는지 알 수 없고, 그러면 백업은 있어도 없는 것과 같다 (ADR-046/047 과 같은 이유).
2474
+ backups: [...base.backups, ...externalBackups]
2414
2475
  };
2415
2476
  ctx.onProgress?.({ type: "baseline-complete", baseline });
2416
2477
  const external = runExternalPhase(ctx);
2417
2478
  const karpathyHook = wireKarpathyHook(spec, external, harnessRoot, projectDir);
2418
2479
  writeInstallLogSafe(
2419
2480
  ctx,
2481
+ externalFiles,
2420
2482
  external,
2421
2483
  base.rootClaudeMdLog,
2422
2484
  previousLog,
@@ -2496,9 +2558,9 @@ function backupEditedPolicyFile(entryTarget, target, source, baseline) {
2496
2558
  if (!POLICY_DIRS.some(({ dir, ext }) => rel.startsWith(`${dir}/`) && rel.endsWith(ext))) {
2497
2559
  return null;
2498
2560
  }
2499
- if (!existsSync12(target)) return null;
2500
- const current = readFileSync11(target, "utf-8");
2501
- if (current === readFileSync11(source, "utf-8")) return null;
2561
+ if (!existsSync13(target)) return null;
2562
+ const current = readFileSync12(target, "utf-8");
2563
+ if (current === readFileSync12(source, "utf-8")) return null;
2502
2564
  const recorded = baseline.get(rel);
2503
2565
  if (recorded !== void 0 && recorded === hashContent(current)) return null;
2504
2566
  return backupFile(target);
@@ -2513,13 +2575,13 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
2513
2575
  }
2514
2576
  const source = join11(templatesDir, entry.source);
2515
2577
  const target = join11(projectDir, entry.target);
2516
- if (!existsSync12(source)) {
2578
+ if (!existsSync13(source)) {
2517
2579
  result.skipped += 1;
2518
2580
  continue;
2519
2581
  }
2520
2582
  if (entry.type === "file") {
2521
2583
  if (entry.target === ".claude/settings.json") {
2522
- const backup = backupFileIfChanged(target, readFileSync11(source, "utf-8"));
2584
+ const backup = backupFileIfChanged(target, readFileSync12(source, "utf-8"));
2523
2585
  if (backup) {
2524
2586
  result.backups.push(backup);
2525
2587
  }
@@ -2538,7 +2600,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
2538
2600
  accumulateCategory(result.categories, entry);
2539
2601
  }
2540
2602
  const hookDir = join11(projectDir, ".claude/hooks");
2541
- if (existsSync12(hookDir)) {
2603
+ if (existsSync13(hookDir)) {
2542
2604
  chmodHooksSync(hookDir);
2543
2605
  }
2544
2606
  writeInstalledTracks(projectDir, manifestSpec.tracks);
@@ -2559,15 +2621,27 @@ function writeEnvironmentFiles(projectDir, tracks) {
2559
2621
  gitignoreNpxSkillsAdded: addGitignoreNpxSkillsAgents(projectDir)
2560
2622
  };
2561
2623
  }
2562
- function runCliTransforms(spec, harnessRoot, projectDir, selectedInternalSkills) {
2624
+ function runCliTransforms(spec, harnessRoot, projectDir, selectedInternalSkills, previousExternal) {
2625
+ const baseline = new Map(previousExternal.map((f) => [f.path, f.sha256]));
2626
+ const externalFiles = [];
2627
+ const externalBackups = [];
2628
+ const absorb = (report) => {
2629
+ for (const f of report.ownership.files) {
2630
+ baseline.set(f.path, f.sha256);
2631
+ externalFiles.push(f);
2632
+ }
2633
+ externalBackups.push(...report.ownership.backupPaths);
2634
+ };
2563
2635
  let codex = null;
2564
2636
  let codexOptIn = null;
2565
2637
  if (spec.cli.includes("codex")) {
2566
2638
  codex = runCodexTransform({
2567
2639
  harnessRoot,
2568
2640
  projectDir,
2569
- selectedInternalSkills
2641
+ selectedInternalSkills,
2642
+ baseline
2570
2643
  });
2644
+ absorb(codex);
2571
2645
  const installScope = spec.scope ?? "project";
2572
2646
  if (installScope === "global" && spec.options.withCodexTrust) {
2573
2647
  codexOptIn = runCodexOptIn({ projectDir });
@@ -2575,17 +2649,20 @@ function runCliTransforms(spec, harnessRoot, projectDir, selectedInternalSkills)
2575
2649
  }
2576
2650
  let opencode = null;
2577
2651
  if (spec.cli.includes("opencode")) {
2578
- opencode = runOpencodeTransform({ harnessRoot, projectDir, selectedInternalSkills });
2652
+ opencode = runOpencodeTransform({ harnessRoot, projectDir, selectedInternalSkills, baseline });
2653
+ absorb(opencode);
2579
2654
  }
2580
2655
  let antigravity = null;
2581
2656
  if (spec.cli.includes("antigravity")) {
2582
2657
  antigravity = runAntigravityTransform({
2583
2658
  harnessRoot,
2584
2659
  projectDir,
2585
- selectedInternalSkills
2660
+ selectedInternalSkills,
2661
+ baseline
2586
2662
  });
2663
+ absorb(antigravity);
2587
2664
  }
2588
- return { codex, codexOptIn, opencode, antigravity };
2665
+ return { codex, codexOptIn, opencode, antigravity, externalFiles, externalBackups };
2589
2666
  }
2590
2667
  function runExternalPhase(ctx) {
2591
2668
  if (ctx.runExternal === null) {
@@ -2623,7 +2700,7 @@ function runExternalPhase(ctx) {
2623
2700
  ctx.onProgress?.({ type: "external-complete", report: external });
2624
2701
  return external;
2625
2702
  }
2626
- function writeInstallLogSafe(ctx, external, rootClaudeMdLog, previousLog, claudeDirMovedAside, rootFiles) {
2703
+ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, previousLog, claudeDirMovedAside, rootFiles) {
2627
2704
  try {
2628
2705
  const log = buildInstallLog(
2629
2706
  ctx.spec,
@@ -2636,10 +2713,12 @@ function writeInstallLogSafe(ctx, external, rootClaudeMdLog, previousLog, claude
2636
2713
  );
2637
2714
  const skillFiles = collectSkillHashes(ctx.projectDir);
2638
2715
  const policyFiles = collectPolicyHashes(ctx.projectDir, join11(ctx.harnessRoot, "templates"));
2716
+ const merged = mergeExternalFiles(ctx.projectDir, previousLog?.externalFiles, externalFiles);
2639
2717
  writeInstallLog(ctx.projectDir, {
2640
2718
  ...log,
2641
2719
  ...skillFiles.length > 0 ? { skillFiles } : {},
2642
- ...policyFiles.length > 0 ? { policyFiles } : {}
2720
+ ...policyFiles.length > 0 ? { policyFiles } : {},
2721
+ ...merged.length > 0 ? { externalFiles: merged } : {}
2643
2722
  });
2644
2723
  } catch (e2) {
2645
2724
  ctx.onProgress?.({
@@ -2665,7 +2744,7 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2665
2744
  const sourceHook = join11(harnessRoot, "templates/hooks/karpathy-gate.sh");
2666
2745
  const targetHook = join11(projectDir, KARPATHY_HOOK_RELPATH);
2667
2746
  let hookScriptCopied = false;
2668
- if (existsSync12(sourceHook)) {
2747
+ if (existsSync13(sourceHook)) {
2669
2748
  copyFile(sourceHook, targetHook);
2670
2749
  try {
2671
2750
  chmodSync2(targetHook, 493);
@@ -2675,8 +2754,8 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2675
2754
  }
2676
2755
  const settingsPath = join11(projectDir, ".claude/settings.json");
2677
2756
  let settingsUpdated = false;
2678
- if (existsSync12(settingsPath)) {
2679
- const raw = readFileSync11(settingsPath, "utf8");
2757
+ if (existsSync13(settingsPath)) {
2758
+ const raw = readFileSync12(settingsPath, "utf8");
2680
2759
  let before;
2681
2760
  try {
2682
2761
  before = JSON.parse(raw);
@@ -2687,7 +2766,7 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2687
2766
  const beforeStr = JSON.stringify(before);
2688
2767
  const afterStr = JSON.stringify(after);
2689
2768
  if (beforeStr !== afterStr) {
2690
- writeFileSync9(settingsPath, `${JSON.stringify(after, null, 2)}
2769
+ writeFileSync7(settingsPath, `${JSON.stringify(after, null, 2)}
2691
2770
  `);
2692
2771
  settingsUpdated = true;
2693
2772
  }
@@ -2696,7 +2775,7 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2696
2775
  }
2697
2776
  function composeAndWriteMcp(harnessRoot, projectDir, spec) {
2698
2777
  const mcpPath = join11(projectDir, ".mcp.json");
2699
- const created = !existsSync12(mcpPath);
2778
+ const created = !existsSync13(mcpPath);
2700
2779
  const composed = composeMcpJson({
2701
2780
  templateMcpPath: join11(harnessRoot, "templates/mcp.json"),
2702
2781
  trackMapPath: join11(harnessRoot, "templates/track-mcp-map.tsv"),
@@ -2760,16 +2839,16 @@ function accumulateCategory(cats, entry) {
2760
2839
  }
2761
2840
  function writeInstalledTracks(projectDir, tracks) {
2762
2841
  const path = join11(projectDir, ".claude/.installed-tracks");
2763
- mkdirSync5(dirname5(path), { recursive: true });
2842
+ mkdirSync6(dirname6(path), { recursive: true });
2764
2843
  const sorted = [...new Set(tracks)].sort().join("\n");
2765
- writeFileSync9(path, `${sorted}
2844
+ writeFileSync7(path, `${sorted}
2766
2845
  `);
2767
2846
  }
2768
2847
  function writeRootClaudeMd(projectDir, tracks) {
2769
2848
  const content = mergeProjectClaude(tracks, { projectName: basename4(projectDir) });
2770
2849
  const target = join11(projectDir, "CLAUDE.md");
2771
2850
  const backup = backupFileIfChanged(target, content);
2772
- writeFileSync9(target, content);
2851
+ writeFileSync7(target, content);
2773
2852
  return { content, backup };
2774
2853
  }
2775
2854
  function chmodHooksSync(hookDir) {
@@ -3450,7 +3529,7 @@ function registerInstallCommand(cli2) {
3450
3529
 
3451
3530
  // src/commands/list.ts
3452
3531
  init_esm_shims();
3453
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
3532
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
3454
3533
  import { join as join12, resolve as resolve3 } from "path";
3455
3534
  function listAction(options = {}, deps = {}) {
3456
3535
  const log = deps.log ?? console.log;
@@ -3515,7 +3594,7 @@ function formatTemplateRows(log, projectDir) {
3515
3594
  const rootMd = log.templates.rootClaudeMd;
3516
3595
  if (rootMd) {
3517
3596
  const path = join12(projectDir, rootMd.path);
3518
- const modified = existsSync13(path) && hashContent(readFileSync12(path, "utf8")) !== rootMd.sha256;
3597
+ const modified = existsSync14(path) && hashContent(readFileSync13(path, "utf8")) !== rootMd.sha256;
3519
3598
  rows.push(
3520
3599
  modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
3521
3600
  );
@@ -3523,7 +3602,7 @@ function formatTemplateRows(log, projectDir) {
3523
3602
  return rows;
3524
3603
  }
3525
3604
  function formatRootFileRows(rootFiles, projectDir) {
3526
- const present = rootFiles.filter((f) => existsSync13(join12(projectDir, f.path)));
3605
+ const present = rootFiles.filter((f) => existsSync14(join12(projectDir, f.path)));
3527
3606
  const width = Math.max(0, ...present.map((f) => f.path.length));
3528
3607
  return present.map(
3529
3608
  (f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(f.change === "created" ? "\uC0DD\uC131" : "\uBCD1\uD569")} ${c.dim(f.notes.join(" / "))}`
@@ -3540,7 +3619,7 @@ function registerListCommand(cli2) {
3540
3619
  // src/commands/uninstall.ts
3541
3620
  init_esm_shims();
3542
3621
  import { spawnSync as spawnSync2 } from "child_process";
3543
- import { existsSync as existsSync14, readFileSync as readFileSync13, rmSync } from "fs";
3622
+ import { existsSync as existsSync15, readFileSync as readFileSync14, rmSync } from "fs";
3544
3623
  import { join as join13, resolve as resolve4 } from "path";
3545
3624
 
3546
3625
  // src/uninstall-interactive.ts
@@ -4826,7 +4905,7 @@ function advisoryLines(plan, targetAssets, projectDir, templatesKept, rootFiles)
4826
4905
  return lines;
4827
4906
  }
4828
4907
  function rootFileAdvisoryLines(rootFiles, projectDir) {
4829
- const present = rootFiles.filter((f) => existsSync14(join13(projectDir, f.path)));
4908
+ const present = rootFiles.filter((f) => existsSync15(join13(projectDir, f.path)));
4830
4909
  if (present.length === 0) return [];
4831
4910
  return [
4832
4911
  "",
@@ -4899,9 +4978,9 @@ function buildProjectReverseStep(asset, spawn) {
4899
4978
  }
4900
4979
  function settingsHasHookCommand(projectDir, command) {
4901
4980
  const path = join13(projectDir, ".claude", "settings.json");
4902
- if (!existsSync14(path)) return false;
4981
+ if (!existsSync15(path)) return false;
4903
4982
  try {
4904
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
4983
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
4905
4984
  return Object.values(parsed.hooks ?? {}).some(
4906
4985
  (matchers) => matchers.some((m) => m.hooks?.some((h2) => h2.command === command))
4907
4986
  );
@@ -4926,7 +5005,7 @@ function manualAdvisoryLines(targetAssets, projectDir) {
4926
5005
  `.claude/settings.json \u2014 hooks.PreToolUse \uC5D0\uC11C \uB2E4\uC74C command \uD56D\uBAA9 \uC0AD\uC81C:
4927
5006
  ${KARPATHY_HOOK_COMMAND}`
4928
5007
  );
4929
- if (existsSync14(join13(projectDir, KARPATHY_HOOK_RELPATH)))
5008
+ if (existsSync15(join13(projectDir, KARPATHY_HOOK_RELPATH)))
4930
5009
  items.push(`\`${KARPATHY_HOOK_RELPATH}\` \u2014 \uC0AD\uC81C`);
4931
5010
  if (items.length === 0) return [];
4932
5011
  return [
@@ -4970,8 +5049,8 @@ function rootClaudeMdModified(log, projectDir) {
4970
5049
  const rootMd = log.templates.rootClaudeMd;
4971
5050
  if (!rootMd) return false;
4972
5051
  const path = join13(projectDir, rootMd.path);
4973
- if (!existsSync14(path)) return false;
4974
- return hashContent(readFileSync13(path, "utf8")) !== rootMd.sha256;
5052
+ if (!existsSync15(path)) return false;
5053
+ return hashContent(readFileSync14(path, "utf8")) !== rootMd.sha256;
4975
5054
  }
4976
5055
  function formatTemplateList(log) {
4977
5056
  const items = [log.templates.claudeDir];
@@ -4983,7 +5062,7 @@ function defaultSpawn2(cmd, args) {
4983
5062
  return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
4984
5063
  }
4985
5064
  function defaultRm(path) {
4986
- if (existsSync14(path)) {
5065
+ if (existsSync15(path)) {
4987
5066
  rmSync(path, { recursive: true, force: true });
4988
5067
  }
4989
5068
  }
@@ -5031,7 +5110,7 @@ import { resolve as resolve5 } from "path";
5031
5110
 
5032
5111
  // src/state.ts
5033
5112
  init_esm_shims();
5034
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
5113
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
5035
5114
  import { join as join14 } from "path";
5036
5115
  var META_FILE = ".claude/.installed-tracks";
5037
5116
  var LEGACY_SIGNATURES = [
@@ -5043,12 +5122,12 @@ var LEGACY_SIGNATURES = [
5043
5122
  ];
5044
5123
  function detectInstallState(projectDir) {
5045
5124
  const claudeDir = join14(projectDir, ".claude");
5046
- const hasClaudeDir = existsSync15(claudeDir);
5125
+ const hasClaudeDir = existsSync16(claudeDir);
5047
5126
  if (!hasClaudeDir) {
5048
5127
  return { state: "new", tracks: [], source: "none", hasClaudeDir: false };
5049
5128
  }
5050
5129
  const metaPath = join14(projectDir, META_FILE);
5051
- if (existsSync15(metaPath)) {
5130
+ if (existsSync16(metaPath)) {
5052
5131
  const tracks2 = readMetafile(metaPath);
5053
5132
  return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
5054
5133
  }
@@ -5056,7 +5135,7 @@ function detectInstallState(projectDir) {
5056
5135
  return { state: "existing", tracks, source: "legacy", hasClaudeDir: true };
5057
5136
  }
5058
5137
  function readMetafile(path) {
5059
- const raw = readFileSync14(path, "utf8");
5138
+ const raw = readFileSync15(path, "utf8");
5060
5139
  const seen = /* @__PURE__ */ new Set();
5061
5140
  for (const line of raw.split(/\s+/)) {
5062
5141
  const trimmed = line.trim();
@@ -5068,12 +5147,12 @@ function readMetafile(path) {
5068
5147
  }
5069
5148
  function inferFromLegacySignatures(projectDir) {
5070
5149
  const rulesDir = join14(projectDir, ".claude/rules");
5071
- if (!existsSync15(rulesDir)) {
5150
+ if (!existsSync16(rulesDir)) {
5072
5151
  return [];
5073
5152
  }
5074
5153
  const found = /* @__PURE__ */ new Set();
5075
5154
  for (const sig of LEGACY_SIGNATURES) {
5076
- if (existsSync15(join14(rulesDir, sig.rule))) {
5155
+ if (existsSync16(join14(rulesDir, sig.rule))) {
5077
5156
  found.add(sig.track);
5078
5157
  }
5079
5158
  }