@uzysjung/agent-harness 26.132.0 → 26.134.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.134.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,59 +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";
916
-
917
- // src/antigravity/transform.ts
918
- init_esm_shims();
919
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
920
- import { basename, join as join2 } from "path";
921
-
922
- // src/codex/agents-md.ts
923
- init_esm_shims();
924
- function renameSlashes(text) {
925
- return text.replaceAll("/uzys:", "/uzys-");
926
- }
927
- function renderAgentsMd(params) {
928
- const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
929
- const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext);
930
- return renameSlashes(replaced);
931
- }
915
+ import { basename as basename4, dirname as dirname6, join as join11, resolve } from "path";
932
916
 
933
- // src/codex/skills.ts
917
+ // src/ci-scaffold.ts
934
918
  init_esm_shims();
935
- function renderBundledSkill(source) {
936
- const trimmed = source.trimEnd();
937
- const lines = trimmed.split(/\r?\n/);
938
- if (lines[0] !== "---") {
939
- return `${portBody(trimmed)}
940
- `;
941
- }
942
- let secondDelimAt = -1;
943
- for (let i = 1; i < lines.length; i++) {
944
- if (lines[i] === "---") {
945
- secondDelimAt = i;
946
- break;
947
- }
948
- }
949
- if (secondDelimAt < 0) {
950
- return `${portBody(trimmed)}
951
- `;
952
- }
953
- const frontmatter = lines.slice(0, secondDelimAt + 1).join("\n");
954
- const body = lines.slice(secondDelimAt + 1).join("\n");
955
- return `${frontmatter}
956
- ${portBody(body)}
957
- `;
958
- }
959
- function portBody(body) {
960
- return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
961
- }
919
+ import { existsSync as existsSync2 } from "fs";
920
+ import { join as join2 } from "path";
962
921
 
963
922
  // src/fs-ops.ts
964
923
  init_esm_shims();
@@ -1050,6 +1009,314 @@ function ensureProjectSkeleton(projectDir) {
1050
1009
  }
1051
1010
  }
1052
1011
 
1012
+ // src/ci-scaffold.ts
1013
+ var PYTHON_TRACKS = "data|csr-fastapi|full";
1014
+ var VARIANTS = [
1015
+ {
1016
+ source: "ci-node.yml",
1017
+ target: "ci.yml",
1018
+ applies: (tracks) => anyTrack(tracks, "csr-*|ssr-*|tooling|full") || !anyTrack(tracks, PYTHON_TRACKS)
1019
+ },
1020
+ {
1021
+ source: "ci-python.yml",
1022
+ target: "ci-python.yml",
1023
+ applies: (tracks) => anyTrack(tracks, PYTHON_TRACKS)
1024
+ },
1025
+ {
1026
+ source: "e2e.yml",
1027
+ target: "e2e.yml",
1028
+ applies: (tracks) => hasUiTrack(tracks)
1029
+ }
1030
+ ];
1031
+ function installCiScaffold(ctx) {
1032
+ const report = { written: [], skippedExisting: [] };
1033
+ for (const v2 of VARIANTS) {
1034
+ if (!v2.applies(ctx.tracks)) continue;
1035
+ const relTarget = `.github/workflows/${v2.target}`;
1036
+ const target = join2(ctx.projectDir, relTarget);
1037
+ if (existsSync2(target)) {
1038
+ report.skippedExisting.push(relTarget);
1039
+ continue;
1040
+ }
1041
+ copyFile(join2(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
1042
+ report.written.push(relTarget);
1043
+ }
1044
+ return report;
1045
+ }
1046
+
1047
+ // src/cli-transforms.ts
1048
+ init_esm_shims();
1049
+
1050
+ // src/antigravity/transform.ts
1051
+ init_esm_shims();
1052
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1053
+ import { basename, join as join4 } from "path";
1054
+
1055
+ // src/codex/agents-md.ts
1056
+ init_esm_shims();
1057
+ function renameSlashes(text) {
1058
+ return text.replaceAll("/uzys:", "/uzys-");
1059
+ }
1060
+ function renderAgentsMd(params) {
1061
+ const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
1062
+ const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext);
1063
+ return renameSlashes(replaced);
1064
+ }
1065
+
1066
+ // src/codex/skills.ts
1067
+ init_esm_shims();
1068
+ function renderBundledSkill(source) {
1069
+ const trimmed = source.trimEnd();
1070
+ const lines = trimmed.split(/\r?\n/);
1071
+ if (lines[0] !== "---") {
1072
+ return `${portBody(trimmed)}
1073
+ `;
1074
+ }
1075
+ let secondDelimAt = -1;
1076
+ for (let i = 1; i < lines.length; i++) {
1077
+ if (lines[i] === "---") {
1078
+ secondDelimAt = i;
1079
+ break;
1080
+ }
1081
+ }
1082
+ if (secondDelimAt < 0) {
1083
+ return `${portBody(trimmed)}
1084
+ `;
1085
+ }
1086
+ const frontmatter = lines.slice(0, secondDelimAt + 1).join("\n");
1087
+ const body = lines.slice(secondDelimAt + 1).join("\n");
1088
+ return `${frontmatter}
1089
+ ${portBody(body)}
1090
+ `;
1091
+ }
1092
+ function portBody(body) {
1093
+ return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
1094
+ }
1095
+
1096
+ // src/owned-write.ts
1097
+ init_esm_shims();
1098
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1099
+ import { dirname as dirname3, relative, sep } from "path";
1100
+
1101
+ // src/install-log.ts
1102
+ init_esm_shims();
1103
+ import { createHash } from "crypto";
1104
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
1105
+ import { dirname as dirname2, join as join3 } from "path";
1106
+ var INSTALL_LOG_FILENAME = ".harness-install.json";
1107
+ var INSTALL_LOG_VERSION = 1;
1108
+ var POLICY_DIRS = [
1109
+ { dir: "rules", ext: ".md" },
1110
+ { dir: "agents", ext: ".md" },
1111
+ { dir: "commands/uzys", ext: ".md" },
1112
+ { dir: "hooks", ext: ".sh" }
1113
+ ];
1114
+ function buildAssetEntries(report, scope) {
1115
+ if (!report) return [];
1116
+ return report.attempted.filter((r) => r.ok).map((r) => assetToLogEntry(r.asset, scope, r.version));
1117
+ }
1118
+ function assetToLogEntry(asset, scope, version) {
1119
+ const detail = methodDetail(asset.method);
1120
+ const entry = {
1121
+ id: asset.id,
1122
+ category: asset.category,
1123
+ method: asset.method.kind,
1124
+ scope,
1125
+ detail
1126
+ };
1127
+ if (version) entry.version = version;
1128
+ return entry;
1129
+ }
1130
+ function methodDetail(method) {
1131
+ switch (method.kind) {
1132
+ case "plugin":
1133
+ return { marketplace: method.marketplace, pluginId: method.pluginId };
1134
+ case "skill":
1135
+ return { source: method.source, ...method.skill ? { skill: method.skill } : {} };
1136
+ case "npm":
1137
+ return { pkg: method.pkg };
1138
+ case "npx-run":
1139
+ return { cmd: method.cmd, args: (method.args ?? []).join(" ") };
1140
+ case "shell-script":
1141
+ return { script: method.script, args: method.args.join(" ") };
1142
+ case "internal":
1143
+ return { key: method.key };
1144
+ }
1145
+ }
1146
+ function buildInstallLog(spec, external, scope, rootClaudeMd, previous, claudeDirMovedAside = false, rootFiles = []) {
1147
+ const templates = {
1148
+ claudeDir: ".claude/",
1149
+ ...spec.cli.includes("codex") ? { codexDir: ".codex/" } : {},
1150
+ ...spec.cli.includes("opencode") ? { opencodeDir: ".opencode/" } : {},
1151
+ ...rootClaudeMd ? { rootClaudeMd } : {}
1152
+ };
1153
+ const log = {
1154
+ schemaVersion: INSTALL_LOG_VERSION,
1155
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1156
+ scope,
1157
+ spec: {
1158
+ tracks: spec.tracks,
1159
+ cli: spec.cli
1160
+ },
1161
+ // 이번 설치가 만든 항목이 이기고, 이번에 안 만든 항목은 이전 값을 그대로 둔다.
1162
+ // (예: claude 로 깔고 나중에 codex 만 추가 설치해도 root CLAUDE.md 기록이 살아남는다)
1163
+ templates: { ...previous?.templates, ...templates },
1164
+ assets: mergeAssets(
1165
+ claudeDirMovedAside ? previous?.assets?.filter(survivesClaudeDirRename) : previous?.assets,
1166
+ buildAssetEntries(external, scope)
1167
+ )
1168
+ };
1169
+ const mergedRootFiles = mergeRootFiles(previous?.rootFiles, rootFiles);
1170
+ if (mergedRootFiles.length > 0) log.rootFiles = mergedRootFiles;
1171
+ return log;
1172
+ }
1173
+ function mergeRootFiles(previous, current) {
1174
+ const byPath = /* @__PURE__ */ new Map();
1175
+ for (const file of [...previous ?? [], ...current]) {
1176
+ const prior = byPath.get(file.path);
1177
+ byPath.set(
1178
+ file.path,
1179
+ prior ? {
1180
+ path: file.path,
1181
+ change: prior.change === "created" ? "created" : file.change,
1182
+ notes: [.../* @__PURE__ */ new Set([...prior.notes, ...file.notes])]
1183
+ } : file
1184
+ );
1185
+ }
1186
+ return [...byPath.values()];
1187
+ }
1188
+ function survivesClaudeDirRename(asset) {
1189
+ if (asset.scope === "global") return true;
1190
+ switch (asset.method) {
1191
+ case "plugin":
1192
+ return true;
1193
+ // `~/.claude/plugins/cache` — 프로젝트 밖
1194
+ case "npm":
1195
+ return true;
1196
+ // `node_modules/`
1197
+ case "skill":
1198
+ return false;
1199
+ // `npx skills add` project scope → `.claude/skills/`
1200
+ case "shell-script":
1201
+ return false;
1202
+ // ecc-prune → `.claude/local-plugins/`
1203
+ case "npx-run":
1204
+ return false;
1205
+ case "internal":
1206
+ return false;
1207
+ }
1208
+ }
1209
+ function mergeAssets(previous, current) {
1210
+ if (!previous || previous.length === 0) return [...current];
1211
+ const currentById = new Map(current.map((a) => [a.id, a]));
1212
+ const previousIds = new Set(previous.map((a) => a.id));
1213
+ return [
1214
+ ...previous.map((a) => currentById.get(a.id) ?? a),
1215
+ ...current.filter((a) => !previousIds.has(a.id))
1216
+ ];
1217
+ }
1218
+ function hashContent(content) {
1219
+ return createHash("sha256").update(content, "utf8").digest("hex");
1220
+ }
1221
+ function isHarnessOwned(baseline, key, current) {
1222
+ const recorded = baseline.get(key);
1223
+ return recorded !== void 0 && recorded === hashContent(current);
1224
+ }
1225
+ function mergeExternalFiles(projectDir, previous, current) {
1226
+ const byPath = /* @__PURE__ */ new Map();
1227
+ for (const file of [...previous ?? [], ...current]) byPath.set(file.path, file);
1228
+ return [...byPath.values()].filter((f) => existsSync3(join3(projectDir, f.path)));
1229
+ }
1230
+ function collectSkillHashes(projectDir) {
1231
+ const skillsDir = join3(projectDir, ".claude/skills");
1232
+ return listFilesRecursive(skillsDir).map((rel) => ({
1233
+ path: rel,
1234
+ sha256: hashContent(readFileSync2(join3(skillsDir, rel), "utf8"))
1235
+ }));
1236
+ }
1237
+ function collectPolicyHashes(projectDir, templatesDir) {
1238
+ const out = [];
1239
+ for (const { dir, ext } of POLICY_DIRS) {
1240
+ const targetDir = join3(projectDir, ".claude", dir);
1241
+ const sourceDir = join3(templatesDir, dir);
1242
+ for (const rel of listFilesRecursive(targetDir)) {
1243
+ if (!rel.endsWith(ext)) continue;
1244
+ if (!existsSync3(join3(sourceDir, rel))) continue;
1245
+ out.push({
1246
+ path: `${dir}/${rel}`,
1247
+ sha256: hashContent(readFileSync2(join3(targetDir, rel), "utf8"))
1248
+ });
1249
+ }
1250
+ }
1251
+ return out;
1252
+ }
1253
+ function writeInstallLog(projectDir, log) {
1254
+ const path = join3(projectDir, ".claude", INSTALL_LOG_FILENAME);
1255
+ mkdirSync2(dirname2(path), { recursive: true });
1256
+ writeFileSync(path, `${JSON.stringify(log, null, 2)}
1257
+ `, "utf8");
1258
+ return path;
1259
+ }
1260
+ function readInstallLog(projectDir) {
1261
+ const path = join3(projectDir, ".claude", INSTALL_LOG_FILENAME);
1262
+ if (!existsSync3(path)) return null;
1263
+ try {
1264
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
1265
+ if (Array.isArray(parsed.assets)) {
1266
+ parsed.assets = parsed.assets.map(
1267
+ (a) => a.method === "npm-global" ? { ...a, method: "npm" } : a
1268
+ );
1269
+ }
1270
+ return parsed;
1271
+ } catch {
1272
+ return null;
1273
+ }
1274
+ }
1275
+ function installLogPath(projectDir) {
1276
+ return join3(projectDir, ".claude", INSTALL_LOG_FILENAME);
1277
+ }
1278
+
1279
+ // src/owned-write.ts
1280
+ function createOwnedWriter(projectDir, baseline, options = {}) {
1281
+ const { now = /* @__PURE__ */ new Date(), refreshOnly = false } = options;
1282
+ const written = /* @__PURE__ */ new Map();
1283
+ const backedUp = [];
1284
+ const backupPaths = [];
1285
+ let updated = 0;
1286
+ return {
1287
+ write(absPath, content) {
1288
+ const rel = relative(projectDir, absPath).split(sep).join("/");
1289
+ const digest = hashContent(content);
1290
+ if (!existsSync4(absPath)) {
1291
+ if (refreshOnly) return false;
1292
+ mkdirSync3(dirname3(absPath), { recursive: true });
1293
+ writeFileSync2(absPath, content);
1294
+ updated++;
1295
+ } else {
1296
+ const current = readFileSync3(absPath, "utf8");
1297
+ if (current !== content) {
1298
+ if (!isHarnessOwned(baseline, rel, current)) {
1299
+ backupPaths.push(backupFile(absPath, now));
1300
+ backedUp.push(rel);
1301
+ }
1302
+ writeFileSync2(absPath, content);
1303
+ updated++;
1304
+ }
1305
+ }
1306
+ written.set(rel, digest);
1307
+ return true;
1308
+ },
1309
+ result() {
1310
+ return {
1311
+ files: [...written].map(([path, sha256]) => ({ path, sha256 })),
1312
+ backedUp: [...backedUp],
1313
+ backupPaths: [...backupPaths],
1314
+ updated
1315
+ };
1316
+ }
1317
+ };
1318
+ }
1319
+
1053
1320
  // src/project-claude-merge.ts
1054
1321
  init_esm_shims();
1055
1322
  var TRACK_DISPLAY_NAMES = {
@@ -1144,97 +1411,58 @@ function expandTracks(tracks) {
1144
1411
 
1145
1412
  // src/antigravity/transform.ts
1146
1413
  function runAntigravityTransform(params) {
1147
- const { harnessRoot, projectDir, selectedInternalSkills = [] } = params;
1148
- const rulesFile = writeRules(harnessRoot, projectDir);
1414
+ const { harnessRoot, projectDir, selectedInternalSkills = [], baseline, refreshOnly } = params;
1415
+ const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
1416
+ const rulesFile = writeRules(harnessRoot, projectDir, writer);
1149
1417
  const skillFiles = [];
1150
1418
  for (const id of selectedInternalSkills) {
1151
- const src = join2(harnessRoot, "templates/skills", id, "SKILL.md");
1152
- if (!existsSync2(src)) {
1419
+ const src = join4(harnessRoot, "templates/skills", id, "SKILL.md");
1420
+ if (!existsSync5(src)) {
1153
1421
  continue;
1154
1422
  }
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")));
1423
+ const target = join4(projectDir, ".agents", "skills", id, "SKILL.md");
1424
+ if (!writer.write(target, renderBundledSkill(readFileSync4(src, "utf8")))) continue;
1159
1425
  skillFiles.push(target);
1160
1426
  }
1161
- return { rulesFile, skillFiles };
1427
+ return {
1428
+ rulesFile,
1429
+ skillFiles,
1430
+ ownership: writer.result()
1431
+ };
1162
1432
  }
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)) {
1433
+ function writeRules(harnessRoot, projectDir, writer) {
1434
+ const claudeMdPath = join4(harnessRoot, "templates/CLAUDE.md");
1435
+ const templatePath = join4(harnessRoot, "templates/antigravity/AGENTS.md.template");
1436
+ if (!existsSync5(claudeMdPath) || !existsSync5(templatePath)) {
1167
1437
  return null;
1168
1438
  }
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");
1439
+ const claudeMd = readFileSync4(claudeMdPath, "utf8");
1440
+ const template = readFileSync4(templatePath, "utf8");
1441
+ const target = join4(projectDir, ".agents", "rules", "uzys-harness.md");
1174
1442
  const rulesOut = renderAgentsMd({
1175
1443
  template,
1176
1444
  claudeMd,
1177
1445
  projectName: basename(projectDir),
1178
1446
  projectContext: renderFillScaffold()
1179
1447
  });
1180
- backupFileIfChanged(target, rulesOut);
1181
- writeFileSync(target, rulesOut);
1182
- return target;
1183
- }
1184
-
1185
- // src/ci-scaffold.ts
1186
- init_esm_shims();
1187
- import { existsSync as existsSync3 } from "fs";
1188
- import { join as join3 } from "path";
1189
- var PYTHON_TRACKS = "data|csr-fastapi|full";
1190
- var VARIANTS = [
1191
- {
1192
- source: "ci-node.yml",
1193
- target: "ci.yml",
1194
- applies: (tracks) => anyTrack(tracks, "csr-*|ssr-*|tooling|full") || !anyTrack(tracks, PYTHON_TRACKS)
1195
- },
1196
- {
1197
- source: "ci-python.yml",
1198
- target: "ci-python.yml",
1199
- applies: (tracks) => anyTrack(tracks, PYTHON_TRACKS)
1200
- },
1201
- {
1202
- source: "e2e.yml",
1203
- target: "e2e.yml",
1204
- applies: (tracks) => hasUiTrack(tracks)
1205
- }
1206
- ];
1207
- function installCiScaffold(ctx) {
1208
- const report = { written: [], skippedExisting: [] };
1209
- for (const v2 of VARIANTS) {
1210
- if (!v2.applies(ctx.tracks)) continue;
1211
- const relTarget = `.github/workflows/${v2.target}`;
1212
- const target = join3(ctx.projectDir, relTarget);
1213
- if (existsSync3(target)) {
1214
- report.skippedExisting.push(relTarget);
1215
- continue;
1216
- }
1217
- copyFile(join3(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
1218
- report.written.push(relTarget);
1219
- }
1220
- return report;
1448
+ return writer.write(target, rulesOut) ? target : null;
1221
1449
  }
1222
1450
 
1223
1451
  // src/codex/opt-in.ts
1224
1452
  init_esm_shims();
1225
1453
  import { homedir } from "os";
1226
- import { join as join4 } from "path";
1454
+ import { join as join5 } from "path";
1227
1455
 
1228
1456
  // src/codex/trust-entry.ts
1229
1457
  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";
1458
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1459
+ import { dirname as dirname4 } from "path";
1232
1460
  var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
1233
1461
  function registerTrustEntry(opts) {
1234
1462
  const { configPath, projectDir } = opts;
1235
1463
  try {
1236
- mkdirSync2(dirname2(configPath), { recursive: true });
1237
- const existing = existsSync4(configPath) ? readFileSync3(configPath, "utf8") : "";
1464
+ mkdirSync4(dirname4(configPath), { recursive: true });
1465
+ const existing = existsSync6(configPath) ? readFileSync5(configPath, "utf8") : "";
1238
1466
  if (hasTrustEntry(existing, projectDir)) {
1239
1467
  return { status: "already-present" };
1240
1468
  }
@@ -1242,7 +1470,7 @@ function registerTrustEntry(opts) {
1242
1470
  [projects."${projectDir}"]
1243
1471
  trust_level = "trusted"
1244
1472
  `;
1245
- writeFileSync2(configPath, existing + block);
1473
+ writeFileSync3(configPath, existing + block);
1246
1474
  return { status: "registered" };
1247
1475
  } catch (e2) {
1248
1476
  return {
@@ -1258,8 +1486,8 @@ function hasTrustEntry(configContent, projectDir) {
1258
1486
 
1259
1487
  // src/codex/opt-in.ts
1260
1488
  function runCodexOptIn(ctx) {
1261
- const codexHome = ctx.codexHome ?? join4(homedir(), ".codex");
1262
- const configPath = join4(codexHome, "config.toml");
1489
+ const codexHome = ctx.codexHome ?? join5(homedir(), ".codex");
1490
+ const configPath = join5(codexHome, "config.toml");
1263
1491
  const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
1264
1492
  return {
1265
1493
  trustEntry: {
@@ -1272,8 +1500,8 @@ function runCodexOptIn(ctx) {
1272
1500
 
1273
1501
  // src/codex/transform.ts
1274
1502
  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";
1503
+ import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
1504
+ import { basename as basename2, join as join6 } from "path";
1277
1505
 
1278
1506
  // src/codex/config-toml.ts
1279
1507
  init_esm_shims();
@@ -1333,90 +1561,331 @@ function renderMcpServers(mcp) {
1333
1561
  function jsonString(s) {
1334
1562
  return JSON.stringify(s);
1335
1563
  }
1336
- function quoteIfNeeded(name) {
1337
- return /^[A-Za-z0-9_-]+$/.test(name) ? name : `"${name}"`;
1564
+ function quoteIfNeeded(name) {
1565
+ return /^[A-Za-z0-9_-]+$/.test(name) ? name : `"${name}"`;
1566
+ }
1567
+
1568
+ // src/codex/transform.ts
1569
+ var HOOK_NAMES = ["session-start"];
1570
+ var ENV_VAR_RENAME = /CLAUDE_PROJECT_DIR/g;
1571
+ function runCodexTransform(params) {
1572
+ const { harnessRoot, projectDir, selectedInternalSkills = [], baseline, refreshOnly } = params;
1573
+ const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
1574
+ const claudeMd = readRequired(join6(harnessRoot, "templates/CLAUDE.md"));
1575
+ const agentsTemplate = readRequired(join6(harnessRoot, "templates/codex/AGENTS.md.template"));
1576
+ const configTemplate = readRequired(join6(harnessRoot, "templates/codex/config.toml.template"));
1577
+ const projectName = basename2(projectDir);
1578
+ const mcp = readOptionalJson(join6(harnessRoot, ".mcp.json"));
1579
+ const agentsMdPath = join6(projectDir, "AGENTS.md");
1580
+ ensureDir(projectDir);
1581
+ const agentsMdOut = renderAgentsMd({
1582
+ template: agentsTemplate,
1583
+ claudeMd,
1584
+ projectName,
1585
+ projectContext: renderFillScaffold()
1586
+ });
1587
+ writer.write(agentsMdPath, agentsMdOut);
1588
+ const configTomlPath = join6(projectDir, ".codex/config.toml");
1589
+ writer.write(
1590
+ configTomlPath,
1591
+ renderConfigToml({
1592
+ template: configTemplate,
1593
+ projectName,
1594
+ projectDir,
1595
+ mcp
1596
+ })
1597
+ );
1598
+ const hookDir = join6(projectDir, ".codex/hooks");
1599
+ const hookFiles = [];
1600
+ for (const hook of HOOK_NAMES) {
1601
+ const src = join6(harnessRoot, "templates/hooks", `${hook}.sh`);
1602
+ if (!existsSync7(src)) {
1603
+ continue;
1604
+ }
1605
+ const ported = readFileSync6(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
1606
+ const target = join6(hookDir, `${hook}.sh`);
1607
+ if (!writer.write(target, ported)) continue;
1608
+ chmodSync(target, 493);
1609
+ hookFiles.push(target);
1610
+ }
1611
+ const skillFiles = [];
1612
+ for (const id of selectedInternalSkills) {
1613
+ const src = join6(harnessRoot, "templates/skills", id, "SKILL.md");
1614
+ if (!existsSync7(src)) {
1615
+ continue;
1616
+ }
1617
+ const target = join6(projectDir, ".agents", "skills", id, "SKILL.md");
1618
+ if (!writer.write(target, renderBundledSkill(readFileSync6(src, "utf8")))) continue;
1619
+ skillFiles.push(target);
1620
+ }
1621
+ return {
1622
+ agentsMdPath,
1623
+ configTomlPath,
1624
+ hookFiles,
1625
+ skillFiles,
1626
+ ownership: writer.result()
1627
+ };
1628
+ }
1629
+ function readRequired(path) {
1630
+ if (!existsSync7(path)) {
1631
+ throw new Error(`Codex transform: required source missing: ${path}`);
1632
+ }
1633
+ return readFileSync6(path, "utf8");
1634
+ }
1635
+ function readOptionalJson(path) {
1636
+ if (!existsSync7(path)) {
1637
+ return null;
1638
+ }
1639
+ try {
1640
+ return JSON.parse(readFileSync6(path, "utf8"));
1641
+ } catch {
1642
+ return null;
1643
+ }
1644
+ }
1645
+
1646
+ // src/opencode/transform.ts
1647
+ init_esm_shims();
1648
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
1649
+ import { basename as basename3, join as join7 } from "path";
1650
+
1651
+ // src/opencode/agents-md.ts
1652
+ init_esm_shims();
1653
+ function renameSlashes2(text) {
1654
+ return text.replaceAll("/uzys:", "/uzys-");
1655
+ }
1656
+ function renderAgentsMd2(params) {
1657
+ const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
1658
+ const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext);
1659
+ return renameSlashes2(replaced);
1660
+ }
1661
+
1662
+ // src/opencode/commands.ts
1663
+ init_esm_shims();
1664
+ function renderCommandFromSkill(source, id, opts) {
1665
+ const { description, body } = parseSkillFrontmatter(source);
1666
+ const finalDescription = description || `${id} (dev-method skill, OpenCode command fallback)`;
1667
+ const escapedDesc = finalDescription.replace(/"/g, '\\"');
1668
+ const renamedBody = renameSlashes2(body).trimEnd();
1669
+ const agent = opts?.shellDependent ? "build" : "plan";
1670
+ return [
1671
+ "---",
1672
+ `description: "${escapedDesc}"`,
1673
+ `agent: ${agent}`,
1674
+ "---",
1675
+ "",
1676
+ renamedBody,
1677
+ ""
1678
+ ].join("\n");
1679
+ }
1680
+ function parseSkillFrontmatter(source) {
1681
+ const lines = source.split(/\r?\n/);
1682
+ if (lines[0] !== "---") {
1683
+ const firstLine = lines[0] ?? "";
1684
+ return { description: firstLine.trim(), body: lines.slice(1).join("\n") };
1685
+ }
1686
+ let secondDelimAt = -1;
1687
+ let description = "";
1688
+ for (let i = 1; i < lines.length; i++) {
1689
+ const line = lines[i] ?? "";
1690
+ if (line === "---") {
1691
+ secondDelimAt = i;
1692
+ break;
1693
+ }
1694
+ const inline = line.match(/^description:\s*(.+)$/);
1695
+ if (!inline) {
1696
+ continue;
1697
+ }
1698
+ const raw = (inline[1] ?? "").trim();
1699
+ if (raw === ">-" || raw === ">" || raw === "|" || raw === "|-") {
1700
+ const collected = [];
1701
+ for (let j3 = i + 1; j3 < lines.length; j3++) {
1702
+ const next = lines[j3] ?? "";
1703
+ if (next === "---") {
1704
+ break;
1705
+ }
1706
+ if (next.trim() === "" || /^\s/.test(next)) {
1707
+ collected.push(next.trim());
1708
+ } else {
1709
+ break;
1710
+ }
1711
+ }
1712
+ description = collected.join(" ").replace(/\s+/g, " ").trim();
1713
+ } else {
1714
+ description = stripQuotes(raw);
1715
+ }
1716
+ }
1717
+ const body = secondDelimAt >= 0 ? lines.slice(secondDelimAt + 1).join("\n").replace(/^\n+/, "") : source;
1718
+ return { description, body };
1719
+ }
1720
+ function stripQuotes(raw) {
1721
+ const trimmed = raw.trim();
1722
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
1723
+ return trimmed.slice(1, -1);
1724
+ }
1725
+ return trimmed;
1726
+ }
1727
+
1728
+ // src/opencode/opencode-json.ts
1729
+ init_esm_shims();
1730
+ function renderOpencodeJson(params) {
1731
+ const config = parseTemplate(params.template);
1732
+ if (params.mcp) {
1733
+ config.mcp = { ...params.mcp.mcpServers };
1734
+ }
1735
+ return `${JSON.stringify(config, null, 2)}
1736
+ `;
1737
+ }
1738
+ function parseTemplate(template) {
1739
+ try {
1740
+ return JSON.parse(template);
1741
+ } catch (err) {
1742
+ const message = err instanceof Error ? err.message : String(err);
1743
+ throw new Error(`opencode.json template invalid JSON: ${message}`);
1744
+ }
1338
1745
  }
1339
1746
 
1340
- // src/codex/transform.ts
1341
- var HOOK_NAMES = ["session-start"];
1342
- var ENV_VAR_RENAME = /CLAUDE_PROJECT_DIR/g;
1343
- 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"));
1348
- const projectName = basename2(projectDir);
1349
- const mcp = readOptionalJson(join5(harnessRoot, ".mcp.json"));
1350
- const agentsMdPath = join5(projectDir, "AGENTS.md");
1747
+ // src/opencode/transform.ts
1748
+ function runOpencodeTransform(params) {
1749
+ const { harnessRoot, projectDir, selectedInternalSkills = [], baseline, refreshOnly } = params;
1750
+ const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
1751
+ const claudeMd = readRequired2(join7(harnessRoot, "templates/CLAUDE.md"));
1752
+ const agentsTemplate = readRequired2(join7(harnessRoot, "templates/opencode/AGENTS.md.template"));
1753
+ const opencodeTemplate = readRequired2(
1754
+ join7(harnessRoot, "templates/opencode/opencode.json.template")
1755
+ );
1756
+ const projectName = basename3(projectDir);
1757
+ const mcp = readOptionalJson2(join7(harnessRoot, ".mcp.json"));
1351
1758
  ensureDir(projectDir);
1352
- const agentsMdOut = renderAgentsMd({
1759
+ const agentsMdPath = join7(projectDir, "AGENTS.md");
1760
+ const agentsMdOut = renderAgentsMd2({
1353
1761
  template: agentsTemplate,
1354
1762
  claudeMd,
1355
1763
  projectName,
1356
1764
  projectContext: renderFillScaffold()
1357
1765
  });
1358
- backupFileIfChanged(agentsMdPath, agentsMdOut);
1359
- writeFileSync3(agentsMdPath, agentsMdOut);
1360
- const configTomlPath = join5(projectDir, ".codex/config.toml");
1361
- ensureDir(join5(projectDir, ".codex"));
1362
- writeFileSync3(
1363
- configTomlPath,
1364
- renderConfigToml({
1365
- template: configTemplate,
1366
- projectName,
1367
- projectDir,
1368
- mcp
1369
- })
1370
- );
1371
- const hookDir = join5(projectDir, ".codex/hooks");
1372
- ensureDir(hookDir);
1373
- const hookFiles = [];
1374
- for (const hook of HOOK_NAMES) {
1375
- const src = join5(harnessRoot, "templates/hooks", `${hook}.sh`);
1376
- if (!existsSync5(src)) {
1377
- continue;
1378
- }
1379
- const ported = readFileSync4(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
1380
- const target = join5(hookDir, `${hook}.sh`);
1381
- writeFileSync3(target, ported);
1382
- chmodSync(target, 493);
1383
- hookFiles.push(target);
1384
- }
1385
- const skillFiles = [];
1766
+ writer.write(agentsMdPath, agentsMdOut);
1767
+ const opencodeJsonPath = join7(projectDir, "opencode.json");
1768
+ writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
1769
+ const cmdDir = join7(projectDir, ".opencode/commands");
1770
+ if (!refreshOnly) ensureDir(cmdDir);
1771
+ const commandFiles = [];
1386
1772
  for (const id of selectedInternalSkills) {
1387
- const src = join5(harnessRoot, "templates/skills", id, "SKILL.md");
1388
- if (!existsSync5(src)) {
1773
+ const src = join7(harnessRoot, "templates/skills", id, "SKILL.md");
1774
+ if (!existsSync8(src)) {
1389
1775
  continue;
1390
1776
  }
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")));
1395
- skillFiles.push(target);
1777
+ const target = join7(cmdDir, `${id}.md`);
1778
+ const shellDependent = existsSync8(join7(harnessRoot, "templates/skills", id, "scripts"));
1779
+ const wrote = writer.write(
1780
+ target,
1781
+ renderCommandFromSkill(readFileSync7(src, "utf8"), id, { shellDependent })
1782
+ );
1783
+ if (!wrote) continue;
1784
+ commandFiles.push(target);
1396
1785
  }
1397
- return { agentsMdPath, configTomlPath, hookFiles, skillFiles };
1786
+ return {
1787
+ agentsMdPath,
1788
+ opencodeJsonPath,
1789
+ commandFiles,
1790
+ ownership: writer.result()
1791
+ };
1398
1792
  }
1399
- function readRequired(path) {
1400
- if (!existsSync5(path)) {
1401
- throw new Error(`Codex transform: required source missing: ${path}`);
1793
+ function readRequired2(path) {
1794
+ if (!existsSync8(path)) {
1795
+ throw new Error(`OpenCode transform: required source missing: ${path}`);
1402
1796
  }
1403
- return readFileSync4(path, "utf8");
1797
+ return readFileSync7(path, "utf8");
1404
1798
  }
1405
- function readOptionalJson(path) {
1406
- if (!existsSync5(path)) {
1799
+ function readOptionalJson2(path) {
1800
+ if (!existsSync8(path)) {
1407
1801
  return null;
1408
1802
  }
1409
1803
  try {
1410
- return JSON.parse(readFileSync4(path, "utf8"));
1804
+ return JSON.parse(readFileSync7(path, "utf8"));
1411
1805
  } catch {
1412
1806
  return null;
1413
1807
  }
1414
1808
  }
1415
1809
 
1810
+ // src/cli-transforms.ts
1811
+ var ALL_CLI_TARGETS = CLI_BASES;
1812
+ function runCliTransforms(params) {
1813
+ const {
1814
+ harnessRoot,
1815
+ projectDir,
1816
+ cli: cli2,
1817
+ selectedInternalSkills,
1818
+ previousExternal,
1819
+ refreshOnly = false,
1820
+ codexTrust = false
1821
+ } = params;
1822
+ const baseline = new Map(previousExternal.map((f) => [f.path, f.sha256]));
1823
+ const externalFiles = [];
1824
+ const externalBackups = [];
1825
+ const externalBackedUp = [];
1826
+ let externalUpdated = 0;
1827
+ const absorb = (report) => {
1828
+ for (const f of report.ownership.files) {
1829
+ baseline.set(f.path, f.sha256);
1830
+ externalFiles.push(f);
1831
+ }
1832
+ externalBackups.push(...report.ownership.backupPaths);
1833
+ externalBackedUp.push(...report.ownership.backedUp);
1834
+ externalUpdated += report.ownership.updated;
1835
+ };
1836
+ let codex = null;
1837
+ let codexOptIn = null;
1838
+ if (cli2.includes("codex")) {
1839
+ codex = runCodexTransform({
1840
+ harnessRoot,
1841
+ projectDir,
1842
+ selectedInternalSkills,
1843
+ baseline,
1844
+ refreshOnly
1845
+ });
1846
+ absorb(codex);
1847
+ if (codexTrust) {
1848
+ codexOptIn = runCodexOptIn({ projectDir });
1849
+ }
1850
+ }
1851
+ let opencode = null;
1852
+ if (cli2.includes("opencode")) {
1853
+ opencode = runOpencodeTransform({
1854
+ harnessRoot,
1855
+ projectDir,
1856
+ selectedInternalSkills,
1857
+ baseline,
1858
+ refreshOnly
1859
+ });
1860
+ absorb(opencode);
1861
+ }
1862
+ let antigravity = null;
1863
+ if (cli2.includes("antigravity")) {
1864
+ antigravity = runAntigravityTransform({
1865
+ harnessRoot,
1866
+ projectDir,
1867
+ selectedInternalSkills,
1868
+ baseline,
1869
+ refreshOnly
1870
+ });
1871
+ absorb(antigravity);
1872
+ }
1873
+ return {
1874
+ codex,
1875
+ codexOptIn,
1876
+ opencode,
1877
+ antigravity,
1878
+ externalFiles,
1879
+ externalBackups,
1880
+ externalUpdated,
1881
+ externalBackedUp
1882
+ };
1883
+ }
1884
+
1416
1885
  // src/env-files.ts
1417
1886
  init_esm_shims();
1418
- import { appendFileSync, existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1419
- import { join as join6 } from "path";
1887
+ import { appendFileSync, existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
1888
+ import { join as join8 } from "path";
1420
1889
  var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
1421
1890
  # Copy to .env (gitignored) and fill in values: cp .env.example .env
1422
1891
 
@@ -1453,24 +1922,24 @@ function writeEnvExample(projectDir, tracks) {
1453
1922
  if (!tracks.some((t) => ENV_EXAMPLE_TRACKS.includes(t))) {
1454
1923
  return false;
1455
1924
  }
1456
- const path = join6(projectDir, ".env.example");
1457
- if (existsSync6(path)) {
1925
+ const path = join8(projectDir, ".env.example");
1926
+ if (existsSync9(path)) {
1458
1927
  return false;
1459
1928
  }
1460
1929
  writeFileSync4(path, ENV_EXAMPLE_BODY);
1461
1930
  return true;
1462
1931
  }
1463
1932
  function addGitignoreEnv(projectDir) {
1464
- const path = join6(projectDir, ".gitignore");
1465
- if (!existsSync6(path)) {
1933
+ const path = join8(projectDir, ".gitignore");
1934
+ if (!existsSync9(path)) {
1466
1935
  return false;
1467
1936
  }
1468
- const content = readFileSync5(path, "utf8");
1937
+ const content = readFileSync8(path, "utf8");
1469
1938
  if (GITIGNORE_ENV_PATTERN.test(content)) {
1470
1939
  return false;
1471
1940
  }
1472
- const sep = content.endsWith("\n") ? "" : "\n";
1473
- appendFileSync(path, `${sep}
1941
+ const sep2 = content.endsWith("\n") ? "" : "\n";
1942
+ appendFileSync(path, `${sep2}
1474
1943
  # Secret env (auto-added by agent-harness install)
1475
1944
  .env
1476
1945
  `);
@@ -1479,11 +1948,11 @@ function addGitignoreEnv(projectDir) {
1479
1948
  var NPX_SKILLS_AGENT_DIRS = [".factory/", ".goose/"];
1480
1949
  var GITIGNORE_NPX_SKILLS_HEADER = "# npx skills add multi-CLI cache (auto-added by agent-harness)";
1481
1950
  function addGitignoreNpxSkillsAgents(projectDir) {
1482
- const path = join6(projectDir, ".gitignore");
1483
- if (!existsSync6(path)) {
1951
+ const path = join8(projectDir, ".gitignore");
1952
+ if (!existsSync9(path)) {
1484
1953
  return [];
1485
1954
  }
1486
- const content = readFileSync5(path, "utf8");
1955
+ const content = readFileSync8(path, "utf8");
1487
1956
  const missing = NPX_SKILLS_AGENT_DIRS.filter((pattern) => {
1488
1957
  const lineRegex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\//g, "/")}\\s*$`, "m");
1489
1958
  return !lineRegex.test(content);
@@ -1491,25 +1960,25 @@ function addGitignoreNpxSkillsAgents(projectDir) {
1491
1960
  if (missing.length === 0) {
1492
1961
  return [];
1493
1962
  }
1494
- const sep = content.endsWith("\n") ? "" : "\n";
1963
+ const sep2 = content.endsWith("\n") ? "" : "\n";
1495
1964
  const block = [GITIGNORE_NPX_SKILLS_HEADER, ...missing].join("\n");
1496
- appendFileSync(path, `${sep}
1965
+ appendFileSync(path, `${sep2}
1497
1966
  ${block}
1498
1967
  `);
1499
1968
  return [...missing];
1500
1969
  }
1501
1970
  function writeMcpAllowlist(projectDir) {
1502
- const allowlistPath = join6(projectDir, ".mcp-allowlist");
1503
- if (existsSync6(allowlistPath)) {
1971
+ const allowlistPath = join8(projectDir, ".mcp-allowlist");
1972
+ if (existsSync9(allowlistPath)) {
1504
1973
  return null;
1505
1974
  }
1506
- const mcpPath = join6(projectDir, ".mcp.json");
1507
- if (!existsSync6(mcpPath)) {
1975
+ const mcpPath = join8(projectDir, ".mcp.json");
1976
+ if (!existsSync9(mcpPath)) {
1508
1977
  return null;
1509
1978
  }
1510
1979
  let names = [];
1511
1980
  try {
1512
- const parsed = JSON.parse(readFileSync5(mcpPath, "utf8"));
1981
+ const parsed = JSON.parse(readFileSync8(mcpPath, "utf8"));
1513
1982
  names = Object.keys(parsed.mcpServers ?? {}).sort();
1514
1983
  } catch {
1515
1984
  return null;
@@ -1532,9 +2001,9 @@ function writeMcpAllowlist(projectDir) {
1532
2001
  // src/external-installer.ts
1533
2002
  init_esm_shims();
1534
2003
  import { spawnSync } from "child_process";
1535
- import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
2004
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
1536
2005
  import { homedir as homedir2 } from "os";
1537
- import { join as join7 } from "path";
2006
+ import { join as join9 } from "path";
1538
2007
  var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
1539
2008
  function selectExternalTargets(assets, ctx) {
1540
2009
  const conditionPassed = filterApplicableAssets(assets, ctx).filter(
@@ -1610,8 +2079,8 @@ function installOne(asset, ctx) {
1610
2079
  cwd
1611
2080
  );
1612
2081
  case "shell-script": {
1613
- const scriptPath = join7(ctx.harnessRoot, method.script);
1614
- if (!existsSync7(scriptPath)) {
2082
+ const scriptPath = join9(ctx.harnessRoot, method.script);
2083
+ if (!existsSync10(scriptPath)) {
1615
2084
  return {
1616
2085
  asset,
1617
2086
  ok: false,
@@ -1672,242 +2141,73 @@ function runSpawn(asset, spawn, cmd, args, cwd) {
1672
2141
  return { asset, ok: false, message: result.error.message };
1673
2142
  }
1674
2143
  if ((result.status ?? 1) !== 0) {
1675
- const stderr = (result.stderr ?? "").trim();
1676
- const tail = stderr.length > 200 ? `${stderr.slice(0, 200)}\u2026` : stderr;
1677
- return {
1678
- asset,
1679
- ok: false,
1680
- message: `${cmd} exited ${result.status}${tail ? `: ${tail}` : ""}`
1681
- };
1682
- }
1683
- return { asset, ok: true };
1684
- }
1685
- function spawnOpts(cwd) {
1686
- return {
1687
- encoding: "utf8",
1688
- stdio: "pipe",
1689
- timeout: DEFAULT_SPAWN_TIMEOUT_MS,
1690
- ...cwd ? { cwd } : {}
1691
- };
1692
- }
1693
- function defaultSpawn(cmd, args, opts) {
1694
- return spawnSync(cmd, [...args], opts);
1695
- }
1696
- function detectVersion(method, spawn) {
1697
- try {
1698
- switch (method.kind) {
1699
- case "plugin": {
1700
- const at2 = method.pluginId.lastIndexOf("@");
1701
- if (at2 <= 0) return void 0;
1702
- const plugin = method.pluginId.slice(0, at2);
1703
- const marketplaceShort = method.pluginId.slice(at2 + 1);
1704
- const cacheBase = join7(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
1705
- if (!existsSync7(cacheBase)) return void 0;
1706
- const versions = readdirSync2(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
1707
- return versions.at(-1);
1708
- }
1709
- case "npm": {
1710
- const npmRoot = getNpmGlobalRoot(spawn);
1711
- 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"));
1715
- return parsed.version;
1716
- }
1717
- default:
1718
- return void 0;
1719
- }
1720
- } catch {
1721
- return void 0;
1722
- }
1723
- }
1724
- var npmGlobalRootCache;
1725
- function getNpmGlobalRoot(spawn) {
1726
- if (npmGlobalRootCache !== void 0) return npmGlobalRootCache || void 0;
1727
- try {
1728
- const r = spawn("npm", ["root", "-g"], spawnOpts());
1729
- if ((r.status ?? 1) === 0) {
1730
- npmGlobalRootCache = (r.stdout ?? "").trim();
1731
- return npmGlobalRootCache || void 0;
1732
- }
1733
- } catch {
1734
- }
1735
- npmGlobalRootCache = "";
1736
- return void 0;
1737
- }
1738
-
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
- ];
2144
+ const stderr = (result.stderr ?? "").trim();
2145
+ const tail = stderr.length > 200 ? `${stderr.slice(0, 200)}\u2026` : stderr;
2146
+ return {
2147
+ asset,
2148
+ ok: false,
2149
+ message: `${cmd} exited ${result.status}${tail ? `: ${tail}` : ""}`
2150
+ };
2151
+ }
2152
+ return { asset, ok: true };
1855
2153
  }
1856
- function hashContent(content) {
1857
- return createHash("sha256").update(content, "utf8").digest("hex");
2154
+ function spawnOpts(cwd) {
2155
+ return {
2156
+ encoding: "utf8",
2157
+ stdio: "pipe",
2158
+ timeout: DEFAULT_SPAWN_TIMEOUT_MS,
2159
+ ...cwd ? { cwd } : {}
2160
+ };
1858
2161
  }
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
- }));
2162
+ function defaultSpawn(cmd, args, opts) {
2163
+ return spawnSync(cmd, [...args], opts);
1865
2164
  }
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
- });
2165
+ function detectVersion(method, spawn) {
2166
+ try {
2167
+ switch (method.kind) {
2168
+ case "plugin": {
2169
+ const at2 = method.pluginId.lastIndexOf("@");
2170
+ if (at2 <= 0) return void 0;
2171
+ const plugin = method.pluginId.slice(0, at2);
2172
+ const marketplaceShort = method.pluginId.slice(at2 + 1);
2173
+ const cacheBase = join9(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
2174
+ if (!existsSync10(cacheBase)) return void 0;
2175
+ const versions = readdirSync2(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
2176
+ return versions.at(-1);
2177
+ }
2178
+ case "npm": {
2179
+ const npmRoot = getNpmGlobalRoot(spawn);
2180
+ if (!npmRoot) return void 0;
2181
+ const pkgJson = join9(npmRoot, method.pkg, "package.json");
2182
+ if (!existsSync10(pkgJson)) return void 0;
2183
+ const parsed = JSON.parse(readFileSync9(pkgJson, "utf8"));
2184
+ return parsed.version;
2185
+ }
2186
+ default:
2187
+ return void 0;
1878
2188
  }
2189
+ } catch {
2190
+ return void 0;
1879
2191
  }
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
2192
  }
1889
- function readInstallLog(projectDir) {
1890
- const path = join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
1891
- if (!existsSync8(path)) return null;
2193
+ var npmGlobalRootCache;
2194
+ function getNpmGlobalRoot(spawn) {
2195
+ if (npmGlobalRootCache !== void 0) return npmGlobalRootCache || void 0;
1892
2196
  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
- );
2197
+ const r = spawn("npm", ["root", "-g"], spawnOpts());
2198
+ if ((r.status ?? 1) === 0) {
2199
+ npmGlobalRootCache = (r.stdout ?? "").trim();
2200
+ return npmGlobalRootCache || void 0;
1898
2201
  }
1899
- return parsed;
1900
2202
  } catch {
1901
- return null;
1902
2203
  }
1903
- }
1904
- function installLogPath(projectDir) {
1905
- return join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
2204
+ npmGlobalRootCache = "";
2205
+ return void 0;
1906
2206
  }
1907
2207
 
1908
2208
  // src/mcp-merge.ts
1909
2209
  init_esm_shims();
1910
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
2210
+ import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
1911
2211
  function parseTrackMcpMap(raw) {
1912
2212
  const rows = [];
1913
2213
  for (const line of raw.split(/\r?\n/)) {
@@ -1958,15 +2258,15 @@ function mergeMcpServers(base, rows, tracks) {
1958
2258
  return out;
1959
2259
  }
1960
2260
  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") : "";
2261
+ const base = JSON.parse(readFileSync10(opts.templateMcpPath, "utf8"));
2262
+ const merged = opts.existingPath && existsSync11(opts.existingPath) ? mergeUserBase(base, opts.existingPath) : base;
2263
+ const mapRaw = existsSync11(opts.trackMapPath) ? readFileSync10(opts.trackMapPath, "utf8") : "";
1964
2264
  const rows = parseTrackMcpMap(mapRaw);
1965
2265
  return mergeMcpServers(merged, rows, opts.tracks);
1966
2266
  }
1967
2267
  function mergeUserBase(base, existingPath) {
1968
2268
  try {
1969
- const existing = JSON.parse(readFileSync8(existingPath, "utf8"));
2269
+ const existing = JSON.parse(readFileSync10(existingPath, "utf8"));
1970
2270
  return {
1971
2271
  ...base,
1972
2272
  mcpServers: { ...base.mcpServers, ...existing.mcpServers }
@@ -1976,168 +2276,10 @@ function mergeUserBase(base, existingPath) {
1976
2276
  }
1977
2277
  }
1978
2278
  function writeMcpJson(path, mcp) {
1979
- writeFileSync6(path, `${JSON.stringify(mcp, null, 2)}
2279
+ writeFileSync5(path, `${JSON.stringify(mcp, null, 2)}
1980
2280
  `);
1981
2281
  }
1982
2282
 
1983
- // src/opencode/transform.ts
1984
- init_esm_shims();
1985
- import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
1986
- import { basename as basename3, join as join9 } from "path";
1987
-
1988
- // src/opencode/agents-md.ts
1989
- init_esm_shims();
1990
- function renameSlashes2(text) {
1991
- return text.replaceAll("/uzys:", "/uzys-");
1992
- }
1993
- function renderAgentsMd2(params) {
1994
- const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
1995
- const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext);
1996
- return renameSlashes2(replaced);
1997
- }
1998
-
1999
- // src/opencode/commands.ts
2000
- init_esm_shims();
2001
- function renderCommandFromSkill(source, id, opts) {
2002
- const { description, body } = parseSkillFrontmatter(source);
2003
- const finalDescription = description || `${id} (dev-method skill, OpenCode command fallback)`;
2004
- const escapedDesc = finalDescription.replace(/"/g, '\\"');
2005
- const renamedBody = renameSlashes2(body).trimEnd();
2006
- const agent = opts?.shellDependent ? "build" : "plan";
2007
- return [
2008
- "---",
2009
- `description: "${escapedDesc}"`,
2010
- `agent: ${agent}`,
2011
- "---",
2012
- "",
2013
- renamedBody,
2014
- ""
2015
- ].join("\n");
2016
- }
2017
- function parseSkillFrontmatter(source) {
2018
- const lines = source.split(/\r?\n/);
2019
- if (lines[0] !== "---") {
2020
- const firstLine = lines[0] ?? "";
2021
- return { description: firstLine.trim(), body: lines.slice(1).join("\n") };
2022
- }
2023
- let secondDelimAt = -1;
2024
- let description = "";
2025
- for (let i = 1; i < lines.length; i++) {
2026
- const line = lines[i] ?? "";
2027
- if (line === "---") {
2028
- secondDelimAt = i;
2029
- break;
2030
- }
2031
- const inline = line.match(/^description:\s*(.+)$/);
2032
- if (!inline) {
2033
- continue;
2034
- }
2035
- const raw = (inline[1] ?? "").trim();
2036
- if (raw === ">-" || raw === ">" || raw === "|" || raw === "|-") {
2037
- const collected = [];
2038
- for (let j3 = i + 1; j3 < lines.length; j3++) {
2039
- const next = lines[j3] ?? "";
2040
- if (next === "---") {
2041
- break;
2042
- }
2043
- if (next.trim() === "" || /^\s/.test(next)) {
2044
- collected.push(next.trim());
2045
- } else {
2046
- break;
2047
- }
2048
- }
2049
- description = collected.join(" ").replace(/\s+/g, " ").trim();
2050
- } else {
2051
- description = stripQuotes(raw);
2052
- }
2053
- }
2054
- const body = secondDelimAt >= 0 ? lines.slice(secondDelimAt + 1).join("\n").replace(/^\n+/, "") : source;
2055
- return { description, body };
2056
- }
2057
- function stripQuotes(raw) {
2058
- const trimmed = raw.trim();
2059
- if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
2060
- return trimmed.slice(1, -1);
2061
- }
2062
- return trimmed;
2063
- }
2064
-
2065
- // src/opencode/opencode-json.ts
2066
- init_esm_shims();
2067
- function renderOpencodeJson(params) {
2068
- const config = parseTemplate(params.template);
2069
- if (params.mcp) {
2070
- config.mcp = { ...params.mcp.mcpServers };
2071
- }
2072
- return `${JSON.stringify(config, null, 2)}
2073
- `;
2074
- }
2075
- function parseTemplate(template) {
2076
- try {
2077
- return JSON.parse(template);
2078
- } catch (err) {
2079
- const message = err instanceof Error ? err.message : String(err);
2080
- throw new Error(`opencode.json template invalid JSON: ${message}`);
2081
- }
2082
- }
2083
-
2084
- // src/opencode/transform.ts
2085
- function runOpencodeTransform(params) {
2086
- const { harnessRoot, projectDir, selectedInternalSkills = [] } = params;
2087
- const claudeMd = readRequired2(join9(harnessRoot, "templates/CLAUDE.md"));
2088
- const agentsTemplate = readRequired2(join9(harnessRoot, "templates/opencode/AGENTS.md.template"));
2089
- const opencodeTemplate = readRequired2(
2090
- join9(harnessRoot, "templates/opencode/opencode.json.template")
2091
- );
2092
- const projectName = basename3(projectDir);
2093
- const mcp = readOptionalJson2(join9(harnessRoot, ".mcp.json"));
2094
- ensureDir(projectDir);
2095
- const agentsMdPath = join9(projectDir, "AGENTS.md");
2096
- const agentsMdOut = renderAgentsMd2({
2097
- template: agentsTemplate,
2098
- claudeMd,
2099
- projectName,
2100
- projectContext: renderFillScaffold()
2101
- });
2102
- backupFileIfChanged(agentsMdPath, agentsMdOut);
2103
- writeFileSync7(agentsMdPath, agentsMdOut);
2104
- const opencodeJsonPath = join9(projectDir, "opencode.json");
2105
- writeFileSync7(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
2106
- const cmdDir = join9(projectDir, ".opencode/commands");
2107
- ensureDir(cmdDir);
2108
- const commandFiles = [];
2109
- for (const id of selectedInternalSkills) {
2110
- const src = join9(harnessRoot, "templates/skills", id, "SKILL.md");
2111
- if (!existsSync10(src)) {
2112
- continue;
2113
- }
2114
- 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
- );
2120
- commandFiles.push(target);
2121
- }
2122
- return { agentsMdPath, opencodeJsonPath, commandFiles };
2123
- }
2124
- function readRequired2(path) {
2125
- if (!existsSync10(path)) {
2126
- throw new Error(`OpenCode transform: required source missing: ${path}`);
2127
- }
2128
- return readFileSync9(path, "utf8");
2129
- }
2130
- function readOptionalJson2(path) {
2131
- if (!existsSync10(path)) {
2132
- return null;
2133
- }
2134
- try {
2135
- return JSON.parse(readFileSync9(path, "utf8"));
2136
- } catch {
2137
- return null;
2138
- }
2139
- }
2140
-
2141
2283
  // src/settings-merge.ts
2142
2284
  init_esm_shims();
2143
2285
  function addPreToolUseHook(settings, matcher, command) {
@@ -2166,14 +2308,14 @@ function addPreToolUseHook(settings, matcher, command) {
2166
2308
  init_esm_shims();
2167
2309
  import {
2168
2310
  copyFileSync as copyFileSync2,
2169
- existsSync as existsSync11,
2170
- mkdirSync as mkdirSync4,
2311
+ existsSync as existsSync12,
2312
+ mkdirSync as mkdirSync5,
2171
2313
  readdirSync as readdirSync3,
2172
- readFileSync as readFileSync10,
2314
+ readFileSync as readFileSync11,
2173
2315
  unlinkSync,
2174
- writeFileSync as writeFileSync8
2316
+ writeFileSync as writeFileSync6
2175
2317
  } from "fs";
2176
- import { dirname as dirname4, join as join10 } from "path";
2318
+ import { dirname as dirname5, join as join10 } from "path";
2177
2319
  function buildUpdateSpec(projectDir, tracks) {
2178
2320
  return {
2179
2321
  tracks: [...tracks],
@@ -2182,7 +2324,7 @@ function buildUpdateSpec(projectDir, tracks) {
2182
2324
  projectDir
2183
2325
  };
2184
2326
  }
2185
- function runUpdateMode(projectDir, templatesDir) {
2327
+ function runUpdateMode(projectDir, templatesDir, harnessRoot) {
2186
2328
  const claudeDir = join10(projectDir, ".claude");
2187
2329
  const report = {
2188
2330
  updated: {},
@@ -2190,7 +2332,9 @@ function runUpdateMode(projectDir, templatesDir) {
2190
2332
  staleHookRefs: [],
2191
2333
  claudeMdUpdated: false,
2192
2334
  skillsBackedUp: [],
2193
- policyBackedUp: []
2335
+ policyBackedUp: [],
2336
+ externalUpdated: 0,
2337
+ externalBackedUp: []
2194
2338
  };
2195
2339
  const policyBase = policyBaseline(projectDir);
2196
2340
  for (const { dir, ext } of POLICY_DIRS) {
@@ -2214,66 +2358,93 @@ function runUpdateMode(projectDir, templatesDir) {
2214
2358
  refreshSkillBaseline(projectDir);
2215
2359
  const claudeMd = join10(claudeDir, "CLAUDE.md");
2216
2360
  const templateMd = join10(templatesDir, "CLAUDE.md");
2217
- if (existsSync11(claudeMd) && existsSync11(templateMd)) {
2361
+ if (existsSync12(claudeMd) && existsSync12(templateMd)) {
2218
2362
  copyFileSync2(templateMd, claudeMd);
2219
2363
  report.claudeMdUpdated = true;
2220
2364
  }
2221
2365
  const settingsPath = join10(claudeDir, "settings.json");
2222
- if (existsSync11(settingsPath)) {
2366
+ if (existsSync12(settingsPath)) {
2223
2367
  report.staleHookRefs = cleanStaleHookRefs(settingsPath, join10(claudeDir, "hooks"));
2224
2368
  }
2369
+ const external = refreshExternalCli(projectDir, harnessRoot);
2370
+ report.externalUpdated = external.externalUpdated;
2371
+ report.externalBackedUp = external.externalBackedUp;
2225
2372
  return report;
2226
2373
  }
2227
- function isHarnessOwned(ctx, rel, current) {
2228
- const recorded = ctx.baseline.get(`${ctx.prefix}/${rel}`);
2229
- return recorded !== void 0 && recorded === hashContent(current);
2374
+ function refreshExternalCli(projectDir, harnessRoot) {
2375
+ const log = readInstallLog(projectDir);
2376
+ const result = runCliTransforms({
2377
+ harnessRoot,
2378
+ projectDir,
2379
+ cli: ALL_CLI_TARGETS,
2380
+ selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS,
2381
+ previousExternal: log?.externalFiles ?? [],
2382
+ refreshOnly: true
2383
+ });
2384
+ if (log) {
2385
+ const merged = mergeExternalFiles(projectDir, log.externalFiles, result.externalFiles);
2386
+ const next = { ...log };
2387
+ if (merged.length > 0) next.externalFiles = merged;
2388
+ else delete next.externalFiles;
2389
+ try {
2390
+ writeInstallLog(projectDir, next);
2391
+ } catch {
2392
+ }
2393
+ }
2394
+ return {
2395
+ externalUpdated: result.externalUpdated,
2396
+ externalBackedUp: result.externalBackedUp
2397
+ };
2398
+ }
2399
+ function isHarnessOwned2(ctx, rel, current) {
2400
+ return isHarnessOwned(ctx.baseline, `${ctx.prefix}/${rel}`, current);
2230
2401
  }
2231
2402
  function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
2232
- if (!existsSync11(target) || !existsSync11(source)) return { updated: 0, backedUp: [] };
2403
+ if (!existsSync12(target) || !existsSync12(source)) return { updated: 0, backedUp: [] };
2233
2404
  let updated = 0;
2234
2405
  const backedUp = [];
2235
2406
  for (const file of readdirSync3(target)) {
2236
2407
  if (!file.endsWith(ext)) continue;
2237
2408
  const targetFile = join10(target, file);
2238
2409
  const sourceFile = join10(source, file);
2239
- if (!existsSync11(sourceFile)) continue;
2240
- const next = readFileSync10(sourceFile, "utf8");
2241
- const current = readFileSync10(targetFile, "utf8");
2410
+ if (!existsSync12(sourceFile)) continue;
2411
+ const next = readFileSync11(sourceFile, "utf8");
2412
+ const current = readFileSync11(targetFile, "utf8");
2242
2413
  if (current === next) continue;
2243
- if (!isHarnessOwned(ctx, file, current)) {
2414
+ if (!isHarnessOwned2(ctx, file, current)) {
2244
2415
  backupFile(targetFile, now);
2245
2416
  backedUp.push(`${ctx.prefix}/${file}`);
2246
2417
  }
2247
- writeFileSync8(targetFile, next);
2418
+ writeFileSync6(targetFile, next);
2248
2419
  updated++;
2249
2420
  }
2250
2421
  return { updated, backedUp };
2251
2422
  }
2252
2423
  function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date()) {
2253
- if (!existsSync11(targetDir) || !existsSync11(sourceDir)) return { updated: 0, backedUp: [] };
2424
+ if (!existsSync12(targetDir) || !existsSync12(sourceDir)) return { updated: 0, backedUp: [] };
2254
2425
  let updated = 0;
2255
2426
  const backedUp = [];
2256
2427
  for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
2257
2428
  if (!skill.isDirectory()) continue;
2258
2429
  const targetSkill = join10(targetDir, skill.name);
2259
- if (!existsSync11(targetSkill)) continue;
2430
+ if (!existsSync12(targetSkill)) continue;
2260
2431
  for (const rel of listFilesRecursive(join10(sourceDir, skill.name))) {
2261
2432
  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);
2433
+ const next = readFileSync11(join10(sourceDir, skill.name, rel), "utf8");
2434
+ if (!existsSync12(targetFile)) {
2435
+ mkdirSync5(dirname5(targetFile), { recursive: true });
2436
+ writeFileSync6(targetFile, next);
2266
2437
  updated++;
2267
2438
  continue;
2268
2439
  }
2269
- const current = readFileSync10(targetFile, "utf8");
2440
+ const current = readFileSync11(targetFile, "utf8");
2270
2441
  if (current === next) continue;
2271
2442
  const recorded = baseline.get(`${skill.name}/${rel}`);
2272
2443
  if (recorded === void 0 || hashContent(current) !== recorded) {
2273
2444
  backupFile(targetFile, now);
2274
2445
  backedUp.push(`${skill.name}/${rel}`);
2275
2446
  }
2276
- writeFileSync8(targetFile, next);
2447
+ writeFileSync6(targetFile, next);
2277
2448
  updated++;
2278
2449
  }
2279
2450
  }
@@ -2312,17 +2483,17 @@ function refreshSkillBaseline(projectDir) {
2312
2483
  }
2313
2484
  }
2314
2485
  function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
2315
- if (!existsSync11(target) || !existsSync11(source)) return [];
2486
+ if (!existsSync12(target) || !existsSync12(source)) return [];
2316
2487
  const removed = [];
2317
2488
  for (const file of readdirSync3(target)) {
2318
2489
  if (!file.endsWith(ext)) continue;
2319
2490
  const sourceFile = join10(source, file);
2320
- if (!existsSync11(sourceFile)) {
2491
+ if (!existsSync12(sourceFile)) {
2321
2492
  const targetFile = join10(target, file);
2322
2493
  if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
2323
2494
  try {
2324
- const current = readFileSync10(targetFile, "utf8");
2325
- if (!isHarnessOwned(ctx, file, current)) backupFile(targetFile, now);
2495
+ const current = readFileSync11(targetFile, "utf8");
2496
+ if (!isHarnessOwned2(ctx, file, current)) backupFile(targetFile, now);
2326
2497
  unlinkSync(targetFile);
2327
2498
  removed.push(file);
2328
2499
  } catch {
@@ -2334,7 +2505,7 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
2334
2505
  function cleanStaleHookRefs(settingsPath, hooksDir) {
2335
2506
  let settings;
2336
2507
  try {
2337
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
2508
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
2338
2509
  } catch {
2339
2510
  return [];
2340
2511
  }
@@ -2353,7 +2524,7 @@ function cleanStaleHookRefs(settingsPath, hooksDir) {
2353
2524
  }
2354
2525
  if (removed.length > 0) {
2355
2526
  const next = { ...settings, hooks: cleanedHooks };
2356
- writeFileSync8(settingsPath, `${JSON.stringify(next, null, 2)}
2527
+ writeFileSync6(settingsPath, `${JSON.stringify(next, null, 2)}
2357
2528
  `);
2358
2529
  }
2359
2530
  return removed;
@@ -2362,7 +2533,7 @@ function keepHookRef(hook, hooksDir, removed) {
2362
2533
  const refMatch = (hook?.command ?? "").match(/\/\.claude\/hooks\/([^"\s/]+\.sh)/);
2363
2534
  if (!refMatch?.[1]) return true;
2364
2535
  const fname = refMatch[1];
2365
- const exists = existsSync11(join10(hooksDir, fname));
2536
+ const exists = existsSync12(join10(hooksDir, fname));
2366
2537
  if (!exists && !removed.includes(fname)) removed.push(fname);
2367
2538
  return exists;
2368
2539
  }
@@ -2375,11 +2546,11 @@ function runInstall(ctx) {
2375
2546
  const { harnessRoot, projectDir, spec } = ctx;
2376
2547
  const mode = ctx.mode ?? "fresh";
2377
2548
  const templatesDir = join11(harnessRoot, "templates");
2378
- if (!existsSync12(templatesDir)) {
2549
+ if (!existsSync13(templatesDir)) {
2379
2550
  throw new Error(`Templates dir not found: ${templatesDir}`);
2380
2551
  }
2381
2552
  const claudeDir = join11(projectDir, ".claude");
2382
- if (mode === "update" && !existsSync12(claudeDir)) {
2553
+ if (mode === "update" && !existsSync13(claudeDir)) {
2383
2554
  throw new Error(`Update mode requires existing .claude/ at ${claudeDir}`);
2384
2555
  }
2385
2556
  const previousLog = readInstallLog(projectDir);
@@ -2396,6 +2567,20 @@ function runInstall(ctx) {
2396
2567
  options: spec.options,
2397
2568
  ...spec.userOverride ? { userOverride: spec.userOverride } : {}
2398
2569
  }) ? installCiScaffold({ harnessRoot, projectDir, tracks: spec.tracks }) : null;
2570
+ const {
2571
+ externalFiles,
2572
+ externalBackups,
2573
+ externalUpdated: _externalUpdated,
2574
+ externalBackedUp: _externalBackedUp,
2575
+ ...cliTransforms
2576
+ } = runCliTransforms({
2577
+ harnessRoot,
2578
+ projectDir,
2579
+ cli: spec.cli,
2580
+ selectedInternalSkills: manifestSpec.selectedInternalSkills,
2581
+ previousExternal: previousLog?.externalFiles ?? [],
2582
+ codexTrust: (spec.scope ?? "project") === "global" && spec.options.withCodexTrust
2583
+ });
2399
2584
  const baseline = {
2400
2585
  filesCopied: base.filesCopied,
2401
2586
  dirsCopied: base.dirsCopied,
@@ -2403,20 +2588,23 @@ function runInstall(ctx) {
2403
2588
  backup: backupPath,
2404
2589
  installedTracks: [...spec.tracks].sort(),
2405
2590
  mcpServers: Object.keys(mcpResult.mcpServers).sort(),
2406
- ...runCliTransforms(spec, harnessRoot, projectDir, manifestSpec.selectedInternalSkills),
2591
+ ...cliTransforms,
2407
2592
  ciScaffold,
2408
2593
  updateMode: null,
2409
2594
  mode,
2410
2595
  envFiles: writeEnvironmentFiles(projectDir, spec.tracks),
2411
2596
  categories: base.categories,
2412
2597
  rootClaudeMd: base.rootClaudeMd,
2413
- backups: base.backups
2598
+ // 외부 CLI 백업도 같은 줄에 노출한다 — 백업이 화면에 안 보이면 사용자는 자기 편집분이
2599
+ // 어디 갔는지 알 수 없고, 그러면 백업은 있어도 없는 것과 같다 (ADR-046/047 과 같은 이유).
2600
+ backups: [...base.backups, ...externalBackups]
2414
2601
  };
2415
2602
  ctx.onProgress?.({ type: "baseline-complete", baseline });
2416
2603
  const external = runExternalPhase(ctx);
2417
2604
  const karpathyHook = wireKarpathyHook(spec, external, harnessRoot, projectDir);
2418
2605
  writeInstallLogSafe(
2419
2606
  ctx,
2607
+ externalFiles,
2420
2608
  external,
2421
2609
  base.rootClaudeMdLog,
2422
2610
  previousLog,
@@ -2431,7 +2619,7 @@ function resolveBackupPath(ctx, mode, claudeDir) {
2431
2619
  return mode === "update" ? copyBackupDir(claudeDir) : backupDir(claudeDir);
2432
2620
  }
2433
2621
  function runUpdateInstall(ctx, templatesDir, backupPath) {
2434
- const updateReport = runUpdateMode(ctx.projectDir, templatesDir);
2622
+ const updateReport = runUpdateMode(ctx.projectDir, templatesDir, ctx.harnessRoot);
2435
2623
  const baseline = {
2436
2624
  filesCopied: 0,
2437
2625
  dirsCopied: 0,
@@ -2496,9 +2684,9 @@ function backupEditedPolicyFile(entryTarget, target, source, baseline) {
2496
2684
  if (!POLICY_DIRS.some(({ dir, ext }) => rel.startsWith(`${dir}/`) && rel.endsWith(ext))) {
2497
2685
  return null;
2498
2686
  }
2499
- if (!existsSync12(target)) return null;
2500
- const current = readFileSync11(target, "utf-8");
2501
- if (current === readFileSync11(source, "utf-8")) return null;
2687
+ if (!existsSync13(target)) return null;
2688
+ const current = readFileSync12(target, "utf-8");
2689
+ if (current === readFileSync12(source, "utf-8")) return null;
2502
2690
  const recorded = baseline.get(rel);
2503
2691
  if (recorded !== void 0 && recorded === hashContent(current)) return null;
2504
2692
  return backupFile(target);
@@ -2513,13 +2701,13 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
2513
2701
  }
2514
2702
  const source = join11(templatesDir, entry.source);
2515
2703
  const target = join11(projectDir, entry.target);
2516
- if (!existsSync12(source)) {
2704
+ if (!existsSync13(source)) {
2517
2705
  result.skipped += 1;
2518
2706
  continue;
2519
2707
  }
2520
2708
  if (entry.type === "file") {
2521
2709
  if (entry.target === ".claude/settings.json") {
2522
- const backup = backupFileIfChanged(target, readFileSync11(source, "utf-8"));
2710
+ const backup = backupFileIfChanged(target, readFileSync12(source, "utf-8"));
2523
2711
  if (backup) {
2524
2712
  result.backups.push(backup);
2525
2713
  }
@@ -2538,7 +2726,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
2538
2726
  accumulateCategory(result.categories, entry);
2539
2727
  }
2540
2728
  const hookDir = join11(projectDir, ".claude/hooks");
2541
- if (existsSync12(hookDir)) {
2729
+ if (existsSync13(hookDir)) {
2542
2730
  chmodHooksSync(hookDir);
2543
2731
  }
2544
2732
  writeInstalledTracks(projectDir, manifestSpec.tracks);
@@ -2559,34 +2747,6 @@ function writeEnvironmentFiles(projectDir, tracks) {
2559
2747
  gitignoreNpxSkillsAdded: addGitignoreNpxSkillsAgents(projectDir)
2560
2748
  };
2561
2749
  }
2562
- function runCliTransforms(spec, harnessRoot, projectDir, selectedInternalSkills) {
2563
- let codex = null;
2564
- let codexOptIn = null;
2565
- if (spec.cli.includes("codex")) {
2566
- codex = runCodexTransform({
2567
- harnessRoot,
2568
- projectDir,
2569
- selectedInternalSkills
2570
- });
2571
- const installScope = spec.scope ?? "project";
2572
- if (installScope === "global" && spec.options.withCodexTrust) {
2573
- codexOptIn = runCodexOptIn({ projectDir });
2574
- }
2575
- }
2576
- let opencode = null;
2577
- if (spec.cli.includes("opencode")) {
2578
- opencode = runOpencodeTransform({ harnessRoot, projectDir, selectedInternalSkills });
2579
- }
2580
- let antigravity = null;
2581
- if (spec.cli.includes("antigravity")) {
2582
- antigravity = runAntigravityTransform({
2583
- harnessRoot,
2584
- projectDir,
2585
- selectedInternalSkills
2586
- });
2587
- }
2588
- return { codex, codexOptIn, opencode, antigravity };
2589
- }
2590
2750
  function runExternalPhase(ctx) {
2591
2751
  if (ctx.runExternal === null) {
2592
2752
  return null;
@@ -2623,7 +2783,7 @@ function runExternalPhase(ctx) {
2623
2783
  ctx.onProgress?.({ type: "external-complete", report: external });
2624
2784
  return external;
2625
2785
  }
2626
- function writeInstallLogSafe(ctx, external, rootClaudeMdLog, previousLog, claudeDirMovedAside, rootFiles) {
2786
+ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, previousLog, claudeDirMovedAside, rootFiles) {
2627
2787
  try {
2628
2788
  const log = buildInstallLog(
2629
2789
  ctx.spec,
@@ -2636,10 +2796,12 @@ function writeInstallLogSafe(ctx, external, rootClaudeMdLog, previousLog, claude
2636
2796
  );
2637
2797
  const skillFiles = collectSkillHashes(ctx.projectDir);
2638
2798
  const policyFiles = collectPolicyHashes(ctx.projectDir, join11(ctx.harnessRoot, "templates"));
2799
+ const merged = mergeExternalFiles(ctx.projectDir, previousLog?.externalFiles, externalFiles);
2639
2800
  writeInstallLog(ctx.projectDir, {
2640
2801
  ...log,
2641
2802
  ...skillFiles.length > 0 ? { skillFiles } : {},
2642
- ...policyFiles.length > 0 ? { policyFiles } : {}
2803
+ ...policyFiles.length > 0 ? { policyFiles } : {},
2804
+ ...merged.length > 0 ? { externalFiles: merged } : {}
2643
2805
  });
2644
2806
  } catch (e2) {
2645
2807
  ctx.onProgress?.({
@@ -2665,7 +2827,7 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2665
2827
  const sourceHook = join11(harnessRoot, "templates/hooks/karpathy-gate.sh");
2666
2828
  const targetHook = join11(projectDir, KARPATHY_HOOK_RELPATH);
2667
2829
  let hookScriptCopied = false;
2668
- if (existsSync12(sourceHook)) {
2830
+ if (existsSync13(sourceHook)) {
2669
2831
  copyFile(sourceHook, targetHook);
2670
2832
  try {
2671
2833
  chmodSync2(targetHook, 493);
@@ -2675,8 +2837,8 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2675
2837
  }
2676
2838
  const settingsPath = join11(projectDir, ".claude/settings.json");
2677
2839
  let settingsUpdated = false;
2678
- if (existsSync12(settingsPath)) {
2679
- const raw = readFileSync11(settingsPath, "utf8");
2840
+ if (existsSync13(settingsPath)) {
2841
+ const raw = readFileSync12(settingsPath, "utf8");
2680
2842
  let before;
2681
2843
  try {
2682
2844
  before = JSON.parse(raw);
@@ -2687,7 +2849,7 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2687
2849
  const beforeStr = JSON.stringify(before);
2688
2850
  const afterStr = JSON.stringify(after);
2689
2851
  if (beforeStr !== afterStr) {
2690
- writeFileSync9(settingsPath, `${JSON.stringify(after, null, 2)}
2852
+ writeFileSync7(settingsPath, `${JSON.stringify(after, null, 2)}
2691
2853
  `);
2692
2854
  settingsUpdated = true;
2693
2855
  }
@@ -2696,7 +2858,7 @@ function wireKarpathyHook(spec, external, harnessRoot, projectDir) {
2696
2858
  }
2697
2859
  function composeAndWriteMcp(harnessRoot, projectDir, spec) {
2698
2860
  const mcpPath = join11(projectDir, ".mcp.json");
2699
- const created = !existsSync12(mcpPath);
2861
+ const created = !existsSync13(mcpPath);
2700
2862
  const composed = composeMcpJson({
2701
2863
  templateMcpPath: join11(harnessRoot, "templates/mcp.json"),
2702
2864
  trackMapPath: join11(harnessRoot, "templates/track-mcp-map.tsv"),
@@ -2760,16 +2922,16 @@ function accumulateCategory(cats, entry) {
2760
2922
  }
2761
2923
  function writeInstalledTracks(projectDir, tracks) {
2762
2924
  const path = join11(projectDir, ".claude/.installed-tracks");
2763
- mkdirSync5(dirname5(path), { recursive: true });
2925
+ mkdirSync6(dirname6(path), { recursive: true });
2764
2926
  const sorted = [...new Set(tracks)].sort().join("\n");
2765
- writeFileSync9(path, `${sorted}
2927
+ writeFileSync7(path, `${sorted}
2766
2928
  `);
2767
2929
  }
2768
2930
  function writeRootClaudeMd(projectDir, tracks) {
2769
2931
  const content = mergeProjectClaude(tracks, { projectName: basename4(projectDir) });
2770
2932
  const target = join11(projectDir, "CLAUDE.md");
2771
2933
  const backup = backupFileIfChanged(target, content);
2772
- writeFileSync9(target, content);
2934
+ writeFileSync7(target, content);
2773
2935
  return { content, backup };
2774
2936
  }
2775
2937
  function chmodHooksSync(hookDir) {
@@ -3111,6 +3273,24 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3111
3273
  )
3112
3274
  );
3113
3275
  }
3276
+ if (baseline.updateMode.externalUpdated > 0) {
3277
+ log(
3278
+ assetRow(
3279
+ "success",
3280
+ "external CLI artifacts",
3281
+ `${baseline.updateMode.externalUpdated} files updated`
3282
+ )
3283
+ );
3284
+ }
3285
+ if (baseline.updateMode.externalBackedUp.length > 0) {
3286
+ log(
3287
+ assetRow(
3288
+ "skip",
3289
+ "edited external CLI files",
3290
+ `${baseline.updateMode.externalBackedUp.length} backed up as *.backup-<time>`
3291
+ )
3292
+ );
3293
+ }
3114
3294
  return;
3115
3295
  }
3116
3296
  if (baseline.backups) {
@@ -3450,7 +3630,7 @@ function registerInstallCommand(cli2) {
3450
3630
 
3451
3631
  // src/commands/list.ts
3452
3632
  init_esm_shims();
3453
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
3633
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
3454
3634
  import { join as join12, resolve as resolve3 } from "path";
3455
3635
  function listAction(options = {}, deps = {}) {
3456
3636
  const log = deps.log ?? console.log;
@@ -3515,7 +3695,7 @@ function formatTemplateRows(log, projectDir) {
3515
3695
  const rootMd = log.templates.rootClaudeMd;
3516
3696
  if (rootMd) {
3517
3697
  const path = join12(projectDir, rootMd.path);
3518
- const modified = existsSync13(path) && hashContent(readFileSync12(path, "utf8")) !== rootMd.sha256;
3698
+ const modified = existsSync14(path) && hashContent(readFileSync13(path, "utf8")) !== rootMd.sha256;
3519
3699
  rows.push(
3520
3700
  modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
3521
3701
  );
@@ -3523,7 +3703,7 @@ function formatTemplateRows(log, projectDir) {
3523
3703
  return rows;
3524
3704
  }
3525
3705
  function formatRootFileRows(rootFiles, projectDir) {
3526
- const present = rootFiles.filter((f) => existsSync13(join12(projectDir, f.path)));
3706
+ const present = rootFiles.filter((f) => existsSync14(join12(projectDir, f.path)));
3527
3707
  const width = Math.max(0, ...present.map((f) => f.path.length));
3528
3708
  return present.map(
3529
3709
  (f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(f.change === "created" ? "\uC0DD\uC131" : "\uBCD1\uD569")} ${c.dim(f.notes.join(" / "))}`
@@ -3540,7 +3720,7 @@ function registerListCommand(cli2) {
3540
3720
  // src/commands/uninstall.ts
3541
3721
  init_esm_shims();
3542
3722
  import { spawnSync as spawnSync2 } from "child_process";
3543
- import { existsSync as existsSync14, readFileSync as readFileSync13, rmSync } from "fs";
3723
+ import { existsSync as existsSync15, readFileSync as readFileSync14, rmSync } from "fs";
3544
3724
  import { join as join13, resolve as resolve4 } from "path";
3545
3725
 
3546
3726
  // src/uninstall-interactive.ts
@@ -4826,7 +5006,7 @@ function advisoryLines(plan, targetAssets, projectDir, templatesKept, rootFiles)
4826
5006
  return lines;
4827
5007
  }
4828
5008
  function rootFileAdvisoryLines(rootFiles, projectDir) {
4829
- const present = rootFiles.filter((f) => existsSync14(join13(projectDir, f.path)));
5009
+ const present = rootFiles.filter((f) => existsSync15(join13(projectDir, f.path)));
4830
5010
  if (present.length === 0) return [];
4831
5011
  return [
4832
5012
  "",
@@ -4899,9 +5079,9 @@ function buildProjectReverseStep(asset, spawn) {
4899
5079
  }
4900
5080
  function settingsHasHookCommand(projectDir, command) {
4901
5081
  const path = join13(projectDir, ".claude", "settings.json");
4902
- if (!existsSync14(path)) return false;
5082
+ if (!existsSync15(path)) return false;
4903
5083
  try {
4904
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
5084
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
4905
5085
  return Object.values(parsed.hooks ?? {}).some(
4906
5086
  (matchers) => matchers.some((m) => m.hooks?.some((h2) => h2.command === command))
4907
5087
  );
@@ -4926,7 +5106,7 @@ function manualAdvisoryLines(targetAssets, projectDir) {
4926
5106
  `.claude/settings.json \u2014 hooks.PreToolUse \uC5D0\uC11C \uB2E4\uC74C command \uD56D\uBAA9 \uC0AD\uC81C:
4927
5107
  ${KARPATHY_HOOK_COMMAND}`
4928
5108
  );
4929
- if (existsSync14(join13(projectDir, KARPATHY_HOOK_RELPATH)))
5109
+ if (existsSync15(join13(projectDir, KARPATHY_HOOK_RELPATH)))
4930
5110
  items.push(`\`${KARPATHY_HOOK_RELPATH}\` \u2014 \uC0AD\uC81C`);
4931
5111
  if (items.length === 0) return [];
4932
5112
  return [
@@ -4970,8 +5150,8 @@ function rootClaudeMdModified(log, projectDir) {
4970
5150
  const rootMd = log.templates.rootClaudeMd;
4971
5151
  if (!rootMd) return false;
4972
5152
  const path = join13(projectDir, rootMd.path);
4973
- if (!existsSync14(path)) return false;
4974
- return hashContent(readFileSync13(path, "utf8")) !== rootMd.sha256;
5153
+ if (!existsSync15(path)) return false;
5154
+ return hashContent(readFileSync14(path, "utf8")) !== rootMd.sha256;
4975
5155
  }
4976
5156
  function formatTemplateList(log) {
4977
5157
  const items = [log.templates.claudeDir];
@@ -4983,7 +5163,7 @@ function defaultSpawn2(cmd, args) {
4983
5163
  return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
4984
5164
  }
4985
5165
  function defaultRm(path) {
4986
- if (existsSync14(path)) {
5166
+ if (existsSync15(path)) {
4987
5167
  rmSync(path, { recursive: true, force: true });
4988
5168
  }
4989
5169
  }
@@ -5031,7 +5211,7 @@ import { resolve as resolve5 } from "path";
5031
5211
 
5032
5212
  // src/state.ts
5033
5213
  init_esm_shims();
5034
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
5214
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
5035
5215
  import { join as join14 } from "path";
5036
5216
  var META_FILE = ".claude/.installed-tracks";
5037
5217
  var LEGACY_SIGNATURES = [
@@ -5043,12 +5223,12 @@ var LEGACY_SIGNATURES = [
5043
5223
  ];
5044
5224
  function detectInstallState(projectDir) {
5045
5225
  const claudeDir = join14(projectDir, ".claude");
5046
- const hasClaudeDir = existsSync15(claudeDir);
5226
+ const hasClaudeDir = existsSync16(claudeDir);
5047
5227
  if (!hasClaudeDir) {
5048
5228
  return { state: "new", tracks: [], source: "none", hasClaudeDir: false };
5049
5229
  }
5050
5230
  const metaPath = join14(projectDir, META_FILE);
5051
- if (existsSync15(metaPath)) {
5231
+ if (existsSync16(metaPath)) {
5052
5232
  const tracks2 = readMetafile(metaPath);
5053
5233
  return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
5054
5234
  }
@@ -5056,7 +5236,7 @@ function detectInstallState(projectDir) {
5056
5236
  return { state: "existing", tracks, source: "legacy", hasClaudeDir: true };
5057
5237
  }
5058
5238
  function readMetafile(path) {
5059
- const raw = readFileSync14(path, "utf8");
5239
+ const raw = readFileSync15(path, "utf8");
5060
5240
  const seen = /* @__PURE__ */ new Set();
5061
5241
  for (const line of raw.split(/\s+/)) {
5062
5242
  const trimmed = line.trim();
@@ -5068,12 +5248,12 @@ function readMetafile(path) {
5068
5248
  }
5069
5249
  function inferFromLegacySignatures(projectDir) {
5070
5250
  const rulesDir = join14(projectDir, ".claude/rules");
5071
- if (!existsSync15(rulesDir)) {
5251
+ if (!existsSync16(rulesDir)) {
5072
5252
  return [];
5073
5253
  }
5074
5254
  const found = /* @__PURE__ */ new Set();
5075
5255
  for (const sig of LEGACY_SIGNATURES) {
5076
- if (existsSync15(join14(rulesDir, sig.rule))) {
5256
+ if (existsSync16(join14(rulesDir, sig.rule))) {
5077
5257
  found.add(sig.track);
5078
5258
  }
5079
5259
  }