@uzysjung/agent-harness 26.133.0 → 26.134.1

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.133.0",
715
+ version: "26.134.1",
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: {
@@ -753,10 +753,10 @@ var package_default = {
753
753
  devDependencies: {
754
754
  "@biomejs/biome": "^2.4.13",
755
755
  "@types/node": "^25.6.0",
756
- "@vitest/coverage-v8": "^2.1.0",
756
+ "@vitest/coverage-v8": "^4.1.10",
757
757
  tsup: "^8.3.0",
758
758
  typescript: "^5.6.0",
759
- vitest: "^2.1.0"
759
+ vitest: "^4.1.10"
760
760
  },
761
761
  license: "MIT",
762
762
  repository: {
@@ -914,56 +914,10 @@ import {
914
914
  } from "fs";
915
915
  import { basename as basename4, dirname as dirname6, join as join11, resolve } from "path";
916
916
 
917
- // src/antigravity/transform.ts
918
- init_esm_shims();
919
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
920
- import { basename, join as join3 } 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
- }
932
-
933
- // src/codex/skills.ts
934
- 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
- }
962
-
963
- // src/owned-write.ts
917
+ // src/ci-scaffold.ts
964
918
  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";
919
+ import { existsSync as existsSync2 } from "fs";
920
+ import { join as join2 } from "path";
967
921
 
968
922
  // src/fs-ops.ts
969
923
  init_esm_shims();
@@ -1055,11 +1009,100 @@ function ensureProjectSkeleton(projectDir) {
1055
1009
  }
1056
1010
  }
1057
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
+
1058
1101
  // src/install-log.ts
1059
1102
  init_esm_shims();
1060
1103
  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";
1104
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
1105
+ import { dirname as dirname2, join as join3 } from "path";
1063
1106
  var INSTALL_LOG_FILENAME = ".harness-install.json";
1064
1107
  var INSTALL_LOG_VERSION = 1;
1065
1108
  var POLICY_DIRS = [
@@ -1182,41 +1225,41 @@ function isHarnessOwned(baseline, key, current) {
1182
1225
  function mergeExternalFiles(projectDir, previous, current) {
1183
1226
  const byPath = /* @__PURE__ */ new Map();
1184
1227
  for (const file of [...previous ?? [], ...current]) byPath.set(file.path, file);
1185
- return [...byPath.values()].filter((f) => existsSync2(join2(projectDir, f.path)));
1228
+ return [...byPath.values()].filter((f) => existsSync3(join3(projectDir, f.path)));
1186
1229
  }
1187
1230
  function collectSkillHashes(projectDir) {
1188
- const skillsDir = join2(projectDir, ".claude/skills");
1231
+ const skillsDir = join3(projectDir, ".claude/skills");
1189
1232
  return listFilesRecursive(skillsDir).map((rel) => ({
1190
1233
  path: rel,
1191
- sha256: hashContent(readFileSync2(join2(skillsDir, rel), "utf8"))
1234
+ sha256: hashContent(readFileSync2(join3(skillsDir, rel), "utf8"))
1192
1235
  }));
1193
1236
  }
1194
1237
  function collectPolicyHashes(projectDir, templatesDir) {
1195
1238
  const out = [];
1196
1239
  for (const { dir, ext } of POLICY_DIRS) {
1197
- const targetDir = join2(projectDir, ".claude", dir);
1198
- const sourceDir = join2(templatesDir, dir);
1240
+ const targetDir = join3(projectDir, ".claude", dir);
1241
+ const sourceDir = join3(templatesDir, dir);
1199
1242
  for (const rel of listFilesRecursive(targetDir)) {
1200
1243
  if (!rel.endsWith(ext)) continue;
1201
- if (!existsSync2(join2(sourceDir, rel))) continue;
1244
+ if (!existsSync3(join3(sourceDir, rel))) continue;
1202
1245
  out.push({
1203
1246
  path: `${dir}/${rel}`,
1204
- sha256: hashContent(readFileSync2(join2(targetDir, rel), "utf8"))
1247
+ sha256: hashContent(readFileSync2(join3(targetDir, rel), "utf8"))
1205
1248
  });
1206
1249
  }
1207
1250
  }
1208
1251
  return out;
1209
1252
  }
1210
1253
  function writeInstallLog(projectDir, log) {
1211
- const path = join2(projectDir, ".claude", INSTALL_LOG_FILENAME);
1254
+ const path = join3(projectDir, ".claude", INSTALL_LOG_FILENAME);
1212
1255
  mkdirSync2(dirname2(path), { recursive: true });
1213
1256
  writeFileSync(path, `${JSON.stringify(log, null, 2)}
1214
1257
  `, "utf8");
1215
1258
  return path;
1216
1259
  }
1217
1260
  function readInstallLog(projectDir) {
1218
- const path = join2(projectDir, ".claude", INSTALL_LOG_FILENAME);
1219
- if (!existsSync2(path)) return null;
1261
+ const path = join3(projectDir, ".claude", INSTALL_LOG_FILENAME);
1262
+ if (!existsSync3(path)) return null;
1220
1263
  try {
1221
1264
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
1222
1265
  if (Array.isArray(parsed.assets)) {
@@ -1230,21 +1273,25 @@ function readInstallLog(projectDir) {
1230
1273
  }
1231
1274
  }
1232
1275
  function installLogPath(projectDir) {
1233
- return join2(projectDir, ".claude", INSTALL_LOG_FILENAME);
1276
+ return join3(projectDir, ".claude", INSTALL_LOG_FILENAME);
1234
1277
  }
1235
1278
 
1236
1279
  // src/owned-write.ts
1237
- function createOwnedWriter(projectDir, baseline, now = /* @__PURE__ */ new Date()) {
1280
+ function createOwnedWriter(projectDir, baseline, options = {}) {
1281
+ const { now = /* @__PURE__ */ new Date(), refreshOnly = false } = options;
1238
1282
  const written = /* @__PURE__ */ new Map();
1239
1283
  const backedUp = [];
1240
1284
  const backupPaths = [];
1285
+ let updated = 0;
1241
1286
  return {
1242
1287
  write(absPath, content) {
1243
1288
  const rel = relative(projectDir, absPath).split(sep).join("/");
1244
1289
  const digest = hashContent(content);
1245
- if (!existsSync3(absPath)) {
1290
+ if (!existsSync4(absPath)) {
1291
+ if (refreshOnly) return false;
1246
1292
  mkdirSync3(dirname3(absPath), { recursive: true });
1247
1293
  writeFileSync2(absPath, content);
1294
+ updated++;
1248
1295
  } else {
1249
1296
  const current = readFileSync3(absPath, "utf8");
1250
1297
  if (current !== content) {
@@ -1253,15 +1300,18 @@ function createOwnedWriter(projectDir, baseline, now = /* @__PURE__ */ new Date(
1253
1300
  backedUp.push(rel);
1254
1301
  }
1255
1302
  writeFileSync2(absPath, content);
1303
+ updated++;
1256
1304
  }
1257
1305
  }
1258
1306
  written.set(rel, digest);
1307
+ return true;
1259
1308
  },
1260
1309
  result() {
1261
1310
  return {
1262
1311
  files: [...written].map(([path, sha256]) => ({ path, sha256 })),
1263
1312
  backedUp: [...backedUp],
1264
- backupPaths: [...backupPaths]
1313
+ backupPaths: [...backupPaths],
1314
+ updated
1265
1315
  };
1266
1316
  }
1267
1317
  };
@@ -1361,17 +1411,17 @@ function expandTracks(tracks) {
1361
1411
 
1362
1412
  // src/antigravity/transform.ts
1363
1413
  function runAntigravityTransform(params) {
1364
- const { harnessRoot, projectDir, selectedInternalSkills = [], baseline } = params;
1365
- const writer = createOwnedWriter(projectDir, baseline);
1414
+ const { harnessRoot, projectDir, selectedInternalSkills = [], baseline, refreshOnly } = params;
1415
+ const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
1366
1416
  const rulesFile = writeRules(harnessRoot, projectDir, writer);
1367
1417
  const skillFiles = [];
1368
1418
  for (const id of selectedInternalSkills) {
1369
- const src = join3(harnessRoot, "templates/skills", id, "SKILL.md");
1370
- if (!existsSync4(src)) {
1419
+ const src = join4(harnessRoot, "templates/skills", id, "SKILL.md");
1420
+ if (!existsSync5(src)) {
1371
1421
  continue;
1372
1422
  }
1373
- const target = join3(projectDir, ".agents", "skills", id, "SKILL.md");
1374
- writer.write(target, renderBundledSkill(readFileSync4(src, "utf8")));
1423
+ const target = join4(projectDir, ".agents", "skills", id, "SKILL.md");
1424
+ if (!writer.write(target, renderBundledSkill(readFileSync4(src, "utf8")))) continue;
1375
1425
  skillFiles.push(target);
1376
1426
  }
1377
1427
  return {
@@ -1381,60 +1431,21 @@ function runAntigravityTransform(params) {
1381
1431
  };
1382
1432
  }
1383
1433
  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)) {
1434
+ const claudeMdPath = join4(harnessRoot, "templates/CLAUDE.md");
1435
+ const templatePath = join4(harnessRoot, "templates/antigravity/AGENTS.md.template");
1436
+ if (!existsSync5(claudeMdPath) || !existsSync5(templatePath)) {
1387
1437
  return null;
1388
1438
  }
1389
1439
  const claudeMd = readFileSync4(claudeMdPath, "utf8");
1390
1440
  const template = readFileSync4(templatePath, "utf8");
1391
- const target = join3(projectDir, ".agents", "rules", "uzys-harness.md");
1441
+ const target = join4(projectDir, ".agents", "rules", "uzys-harness.md");
1392
1442
  const rulesOut = renderAgentsMd({
1393
1443
  template,
1394
1444
  claudeMd,
1395
1445
  projectName: basename(projectDir),
1396
1446
  projectContext: renderFillScaffold()
1397
1447
  });
1398
- writer.write(target, rulesOut);
1399
- return target;
1400
- }
1401
-
1402
- // src/ci-scaffold.ts
1403
- init_esm_shims();
1404
- import { existsSync as existsSync5 } from "fs";
1405
- import { join as join4 } from "path";
1406
- var PYTHON_TRACKS = "data|csr-fastapi|full";
1407
- var VARIANTS = [
1408
- {
1409
- source: "ci-node.yml",
1410
- target: "ci.yml",
1411
- applies: (tracks) => anyTrack(tracks, "csr-*|ssr-*|tooling|full") || !anyTrack(tracks, PYTHON_TRACKS)
1412
- },
1413
- {
1414
- source: "ci-python.yml",
1415
- target: "ci-python.yml",
1416
- applies: (tracks) => anyTrack(tracks, PYTHON_TRACKS)
1417
- },
1418
- {
1419
- source: "e2e.yml",
1420
- target: "e2e.yml",
1421
- applies: (tracks) => hasUiTrack(tracks)
1422
- }
1423
- ];
1424
- function installCiScaffold(ctx) {
1425
- const report = { written: [], skippedExisting: [] };
1426
- for (const v2 of VARIANTS) {
1427
- if (!v2.applies(ctx.tracks)) continue;
1428
- const relTarget = `.github/workflows/${v2.target}`;
1429
- const target = join4(ctx.projectDir, relTarget);
1430
- if (existsSync5(target)) {
1431
- report.skippedExisting.push(relTarget);
1432
- continue;
1433
- }
1434
- copyFile(join4(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
1435
- report.written.push(relTarget);
1436
- }
1437
- return report;
1448
+ return writer.write(target, rulesOut) ? target : null;
1438
1449
  }
1439
1450
 
1440
1451
  // src/codex/opt-in.ts
@@ -1558,8 +1569,8 @@ function quoteIfNeeded(name) {
1558
1569
  var HOOK_NAMES = ["session-start"];
1559
1570
  var ENV_VAR_RENAME = /CLAUDE_PROJECT_DIR/g;
1560
1571
  function runCodexTransform(params) {
1561
- const { harnessRoot, projectDir, selectedInternalSkills = [], baseline } = params;
1562
- const writer = createOwnedWriter(projectDir, baseline);
1572
+ const { harnessRoot, projectDir, selectedInternalSkills = [], baseline, refreshOnly } = params;
1573
+ const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
1563
1574
  const claudeMd = readRequired(join6(harnessRoot, "templates/CLAUDE.md"));
1564
1575
  const agentsTemplate = readRequired(join6(harnessRoot, "templates/codex/AGENTS.md.template"));
1565
1576
  const configTemplate = readRequired(join6(harnessRoot, "templates/codex/config.toml.template"));
@@ -1593,7 +1604,7 @@ function runCodexTransform(params) {
1593
1604
  }
1594
1605
  const ported = readFileSync6(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
1595
1606
  const target = join6(hookDir, `${hook}.sh`);
1596
- writer.write(target, ported);
1607
+ if (!writer.write(target, ported)) continue;
1597
1608
  chmodSync(target, 493);
1598
1609
  hookFiles.push(target);
1599
1610
  }
@@ -1604,7 +1615,7 @@ function runCodexTransform(params) {
1604
1615
  continue;
1605
1616
  }
1606
1617
  const target = join6(projectDir, ".agents", "skills", id, "SKILL.md");
1607
- writer.write(target, renderBundledSkill(readFileSync6(src, "utf8")));
1618
+ if (!writer.write(target, renderBundledSkill(readFileSync6(src, "utf8")))) continue;
1608
1619
  skillFiles.push(target);
1609
1620
  }
1610
1621
  return {
@@ -1619,23 +1630,262 @@ function readRequired(path) {
1619
1630
  if (!existsSync7(path)) {
1620
1631
  throw new Error(`Codex transform: required source missing: ${path}`);
1621
1632
  }
1622
- return readFileSync6(path, "utf8");
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
+ }
1745
+ }
1746
+
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"));
1758
+ ensureDir(projectDir);
1759
+ const agentsMdPath = join7(projectDir, "AGENTS.md");
1760
+ const agentsMdOut = renderAgentsMd2({
1761
+ template: agentsTemplate,
1762
+ claudeMd,
1763
+ projectName,
1764
+ projectContext: renderFillScaffold()
1765
+ });
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 = [];
1772
+ for (const id of selectedInternalSkills) {
1773
+ const src = join7(harnessRoot, "templates/skills", id, "SKILL.md");
1774
+ if (!existsSync8(src)) {
1775
+ continue;
1776
+ }
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);
1785
+ }
1786
+ return {
1787
+ agentsMdPath,
1788
+ opencodeJsonPath,
1789
+ commandFiles,
1790
+ ownership: writer.result()
1791
+ };
1792
+ }
1793
+ function readRequired2(path) {
1794
+ if (!existsSync8(path)) {
1795
+ throw new Error(`OpenCode transform: required source missing: ${path}`);
1796
+ }
1797
+ return readFileSync7(path, "utf8");
1623
1798
  }
1624
- function readOptionalJson(path) {
1625
- if (!existsSync7(path)) {
1799
+ function readOptionalJson2(path) {
1800
+ if (!existsSync8(path)) {
1626
1801
  return null;
1627
1802
  }
1628
1803
  try {
1629
- return JSON.parse(readFileSync6(path, "utf8"));
1804
+ return JSON.parse(readFileSync7(path, "utf8"));
1630
1805
  } catch {
1631
1806
  return null;
1632
1807
  }
1633
1808
  }
1634
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
+
1635
1885
  // src/env-files.ts
1636
1886
  init_esm_shims();
1637
- import { appendFileSync, existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
1638
- import { join as join7 } from "path";
1887
+ import { appendFileSync, existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
1888
+ import { join as join8 } from "path";
1639
1889
  var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
1640
1890
  # Copy to .env (gitignored) and fill in values: cp .env.example .env
1641
1891
 
@@ -1672,19 +1922,19 @@ function writeEnvExample(projectDir, tracks) {
1672
1922
  if (!tracks.some((t) => ENV_EXAMPLE_TRACKS.includes(t))) {
1673
1923
  return false;
1674
1924
  }
1675
- const path = join7(projectDir, ".env.example");
1676
- if (existsSync8(path)) {
1925
+ const path = join8(projectDir, ".env.example");
1926
+ if (existsSync9(path)) {
1677
1927
  return false;
1678
1928
  }
1679
1929
  writeFileSync4(path, ENV_EXAMPLE_BODY);
1680
1930
  return true;
1681
1931
  }
1682
1932
  function addGitignoreEnv(projectDir) {
1683
- const path = join7(projectDir, ".gitignore");
1684
- if (!existsSync8(path)) {
1933
+ const path = join8(projectDir, ".gitignore");
1934
+ if (!existsSync9(path)) {
1685
1935
  return false;
1686
1936
  }
1687
- const content = readFileSync7(path, "utf8");
1937
+ const content = readFileSync8(path, "utf8");
1688
1938
  if (GITIGNORE_ENV_PATTERN.test(content)) {
1689
1939
  return false;
1690
1940
  }
@@ -1698,11 +1948,11 @@ function addGitignoreEnv(projectDir) {
1698
1948
  var NPX_SKILLS_AGENT_DIRS = [".factory/", ".goose/"];
1699
1949
  var GITIGNORE_NPX_SKILLS_HEADER = "# npx skills add multi-CLI cache (auto-added by agent-harness)";
1700
1950
  function addGitignoreNpxSkillsAgents(projectDir) {
1701
- const path = join7(projectDir, ".gitignore");
1702
- if (!existsSync8(path)) {
1951
+ const path = join8(projectDir, ".gitignore");
1952
+ if (!existsSync9(path)) {
1703
1953
  return [];
1704
1954
  }
1705
- const content = readFileSync7(path, "utf8");
1955
+ const content = readFileSync8(path, "utf8");
1706
1956
  const missing = NPX_SKILLS_AGENT_DIRS.filter((pattern) => {
1707
1957
  const lineRegex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\//g, "/")}\\s*$`, "m");
1708
1958
  return !lineRegex.test(content);
@@ -1718,17 +1968,17 @@ ${block}
1718
1968
  return [...missing];
1719
1969
  }
1720
1970
  function writeMcpAllowlist(projectDir) {
1721
- const allowlistPath = join7(projectDir, ".mcp-allowlist");
1722
- if (existsSync8(allowlistPath)) {
1971
+ const allowlistPath = join8(projectDir, ".mcp-allowlist");
1972
+ if (existsSync9(allowlistPath)) {
1723
1973
  return null;
1724
1974
  }
1725
- const mcpPath = join7(projectDir, ".mcp.json");
1726
- if (!existsSync8(mcpPath)) {
1975
+ const mcpPath = join8(projectDir, ".mcp.json");
1976
+ if (!existsSync9(mcpPath)) {
1727
1977
  return null;
1728
1978
  }
1729
1979
  let names = [];
1730
1980
  try {
1731
- const parsed = JSON.parse(readFileSync7(mcpPath, "utf8"));
1981
+ const parsed = JSON.parse(readFileSync8(mcpPath, "utf8"));
1732
1982
  names = Object.keys(parsed.mcpServers ?? {}).sort();
1733
1983
  } catch {
1734
1984
  return null;
@@ -1751,9 +2001,9 @@ function writeMcpAllowlist(projectDir) {
1751
2001
  // src/external-installer.ts
1752
2002
  init_esm_shims();
1753
2003
  import { spawnSync } from "child_process";
1754
- import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
2004
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
1755
2005
  import { homedir as homedir2 } from "os";
1756
- import { join as join8 } from "path";
2006
+ import { join as join9 } from "path";
1757
2007
  var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
1758
2008
  function selectExternalTargets(assets, ctx) {
1759
2009
  const conditionPassed = filterApplicableAssets(assets, ctx).filter(
@@ -1829,8 +2079,8 @@ function installOne(asset, ctx) {
1829
2079
  cwd
1830
2080
  );
1831
2081
  case "shell-script": {
1832
- const scriptPath = join8(ctx.harnessRoot, method.script);
1833
- if (!existsSync9(scriptPath)) {
2082
+ const scriptPath = join9(ctx.harnessRoot, method.script);
2083
+ if (!existsSync10(scriptPath)) {
1834
2084
  return {
1835
2085
  asset,
1836
2086
  ok: false,
@@ -1920,17 +2170,17 @@ function detectVersion(method, spawn) {
1920
2170
  if (at2 <= 0) return void 0;
1921
2171
  const plugin = method.pluginId.slice(0, at2);
1922
2172
  const marketplaceShort = method.pluginId.slice(at2 + 1);
1923
- const cacheBase = join8(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
1924
- if (!existsSync9(cacheBase)) return void 0;
2173
+ const cacheBase = join9(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
2174
+ if (!existsSync10(cacheBase)) return void 0;
1925
2175
  const versions = readdirSync2(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
1926
2176
  return versions.at(-1);
1927
2177
  }
1928
2178
  case "npm": {
1929
2179
  const npmRoot = getNpmGlobalRoot(spawn);
1930
2180
  if (!npmRoot) return void 0;
1931
- const pkgJson = join8(npmRoot, method.pkg, "package.json");
1932
- if (!existsSync9(pkgJson)) return void 0;
1933
- const parsed = JSON.parse(readFileSync8(pkgJson, "utf8"));
2181
+ const pkgJson = join9(npmRoot, method.pkg, "package.json");
2182
+ if (!existsSync10(pkgJson)) return void 0;
2183
+ const parsed = JSON.parse(readFileSync9(pkgJson, "utf8"));
1934
2184
  return parsed.version;
1935
2185
  }
1936
2186
  default:
@@ -1957,7 +2207,7 @@ function getNpmGlobalRoot(spawn) {
1957
2207
 
1958
2208
  // src/mcp-merge.ts
1959
2209
  init_esm_shims();
1960
- import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
2210
+ import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
1961
2211
  function parseTrackMcpMap(raw) {
1962
2212
  const rows = [];
1963
2213
  for (const line of raw.split(/\r?\n/)) {
@@ -2008,15 +2258,15 @@ function mergeMcpServers(base, rows, tracks) {
2008
2258
  return out;
2009
2259
  }
2010
2260
  function composeMcpJson(opts) {
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") : "";
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") : "";
2014
2264
  const rows = parseTrackMcpMap(mapRaw);
2015
2265
  return mergeMcpServers(merged, rows, opts.tracks);
2016
2266
  }
2017
2267
  function mergeUserBase(base, existingPath) {
2018
2268
  try {
2019
- const existing = JSON.parse(readFileSync9(existingPath, "utf8"));
2269
+ const existing = JSON.parse(readFileSync10(existingPath, "utf8"));
2020
2270
  return {
2021
2271
  ...base,
2022
2272
  mcpServers: { ...base.mcpServers, ...existing.mcpServers }
@@ -2030,166 +2280,6 @@ function writeMcpJson(path, mcp) {
2030
2280
  `);
2031
2281
  }
2032
2282
 
2033
- // src/opencode/transform.ts
2034
- init_esm_shims();
2035
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
2036
- import { basename as basename3, join as join9 } from "path";
2037
-
2038
- // src/opencode/agents-md.ts
2039
- init_esm_shims();
2040
- function renameSlashes2(text) {
2041
- return text.replaceAll("/uzys:", "/uzys-");
2042
- }
2043
- function renderAgentsMd2(params) {
2044
- const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
2045
- const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext);
2046
- return renameSlashes2(replaced);
2047
- }
2048
-
2049
- // src/opencode/commands.ts
2050
- init_esm_shims();
2051
- function renderCommandFromSkill(source, id, opts) {
2052
- const { description, body } = parseSkillFrontmatter(source);
2053
- const finalDescription = description || `${id} (dev-method skill, OpenCode command fallback)`;
2054
- const escapedDesc = finalDescription.replace(/"/g, '\\"');
2055
- const renamedBody = renameSlashes2(body).trimEnd();
2056
- const agent = opts?.shellDependent ? "build" : "plan";
2057
- return [
2058
- "---",
2059
- `description: "${escapedDesc}"`,
2060
- `agent: ${agent}`,
2061
- "---",
2062
- "",
2063
- renamedBody,
2064
- ""
2065
- ].join("\n");
2066
- }
2067
- function parseSkillFrontmatter(source) {
2068
- const lines = source.split(/\r?\n/);
2069
- if (lines[0] !== "---") {
2070
- const firstLine = lines[0] ?? "";
2071
- return { description: firstLine.trim(), body: lines.slice(1).join("\n") };
2072
- }
2073
- let secondDelimAt = -1;
2074
- let description = "";
2075
- for (let i = 1; i < lines.length; i++) {
2076
- const line = lines[i] ?? "";
2077
- if (line === "---") {
2078
- secondDelimAt = i;
2079
- break;
2080
- }
2081
- const inline = line.match(/^description:\s*(.+)$/);
2082
- if (!inline) {
2083
- continue;
2084
- }
2085
- const raw = (inline[1] ?? "").trim();
2086
- if (raw === ">-" || raw === ">" || raw === "|" || raw === "|-") {
2087
- const collected = [];
2088
- for (let j3 = i + 1; j3 < lines.length; j3++) {
2089
- const next = lines[j3] ?? "";
2090
- if (next === "---") {
2091
- break;
2092
- }
2093
- if (next.trim() === "" || /^\s/.test(next)) {
2094
- collected.push(next.trim());
2095
- } else {
2096
- break;
2097
- }
2098
- }
2099
- description = collected.join(" ").replace(/\s+/g, " ").trim();
2100
- } else {
2101
- description = stripQuotes(raw);
2102
- }
2103
- }
2104
- const body = secondDelimAt >= 0 ? lines.slice(secondDelimAt + 1).join("\n").replace(/^\n+/, "") : source;
2105
- return { description, body };
2106
- }
2107
- function stripQuotes(raw) {
2108
- const trimmed = raw.trim();
2109
- if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
2110
- return trimmed.slice(1, -1);
2111
- }
2112
- return trimmed;
2113
- }
2114
-
2115
- // src/opencode/opencode-json.ts
2116
- init_esm_shims();
2117
- function renderOpencodeJson(params) {
2118
- const config = parseTemplate(params.template);
2119
- if (params.mcp) {
2120
- config.mcp = { ...params.mcp.mcpServers };
2121
- }
2122
- return `${JSON.stringify(config, null, 2)}
2123
- `;
2124
- }
2125
- function parseTemplate(template) {
2126
- try {
2127
- return JSON.parse(template);
2128
- } catch (err) {
2129
- const message = err instanceof Error ? err.message : String(err);
2130
- throw new Error(`opencode.json template invalid JSON: ${message}`);
2131
- }
2132
- }
2133
-
2134
- // src/opencode/transform.ts
2135
- function runOpencodeTransform(params) {
2136
- const { harnessRoot, projectDir, selectedInternalSkills = [], baseline } = params;
2137
- const writer = createOwnedWriter(projectDir, baseline);
2138
- const claudeMd = readRequired2(join9(harnessRoot, "templates/CLAUDE.md"));
2139
- const agentsTemplate = readRequired2(join9(harnessRoot, "templates/opencode/AGENTS.md.template"));
2140
- const opencodeTemplate = readRequired2(
2141
- join9(harnessRoot, "templates/opencode/opencode.json.template")
2142
- );
2143
- const projectName = basename3(projectDir);
2144
- const mcp = readOptionalJson2(join9(harnessRoot, ".mcp.json"));
2145
- ensureDir(projectDir);
2146
- const agentsMdPath = join9(projectDir, "AGENTS.md");
2147
- const agentsMdOut = renderAgentsMd2({
2148
- template: agentsTemplate,
2149
- claudeMd,
2150
- projectName,
2151
- projectContext: renderFillScaffold()
2152
- });
2153
- writer.write(agentsMdPath, agentsMdOut);
2154
- const opencodeJsonPath = join9(projectDir, "opencode.json");
2155
- writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
2156
- const cmdDir = join9(projectDir, ".opencode/commands");
2157
- ensureDir(cmdDir);
2158
- const commandFiles = [];
2159
- for (const id of selectedInternalSkills) {
2160
- const src = join9(harnessRoot, "templates/skills", id, "SKILL.md");
2161
- if (!existsSync11(src)) {
2162
- continue;
2163
- }
2164
- const target = join9(cmdDir, `${id}.md`);
2165
- const shellDependent = existsSync11(join9(harnessRoot, "templates/skills", id, "scripts"));
2166
- writer.write(target, renderCommandFromSkill(readFileSync10(src, "utf8"), id, { shellDependent }));
2167
- commandFiles.push(target);
2168
- }
2169
- return {
2170
- agentsMdPath,
2171
- opencodeJsonPath,
2172
- commandFiles,
2173
- ownership: writer.result()
2174
- };
2175
- }
2176
- function readRequired2(path) {
2177
- if (!existsSync11(path)) {
2178
- throw new Error(`OpenCode transform: required source missing: ${path}`);
2179
- }
2180
- return readFileSync10(path, "utf8");
2181
- }
2182
- function readOptionalJson2(path) {
2183
- if (!existsSync11(path)) {
2184
- return null;
2185
- }
2186
- try {
2187
- return JSON.parse(readFileSync10(path, "utf8"));
2188
- } catch {
2189
- return null;
2190
- }
2191
- }
2192
-
2193
2283
  // src/settings-merge.ts
2194
2284
  init_esm_shims();
2195
2285
  function addPreToolUseHook(settings, matcher, command) {
@@ -2234,7 +2324,7 @@ function buildUpdateSpec(projectDir, tracks) {
2234
2324
  projectDir
2235
2325
  };
2236
2326
  }
2237
- function runUpdateMode(projectDir, templatesDir) {
2327
+ function runUpdateMode(projectDir, templatesDir, harnessRoot) {
2238
2328
  const claudeDir = join10(projectDir, ".claude");
2239
2329
  const report = {
2240
2330
  updated: {},
@@ -2242,7 +2332,9 @@ function runUpdateMode(projectDir, templatesDir) {
2242
2332
  staleHookRefs: [],
2243
2333
  claudeMdUpdated: false,
2244
2334
  skillsBackedUp: [],
2245
- policyBackedUp: []
2335
+ policyBackedUp: [],
2336
+ externalUpdated: 0,
2337
+ externalBackedUp: []
2246
2338
  };
2247
2339
  const policyBase = policyBaseline(projectDir);
2248
2340
  for (const { dir, ext } of POLICY_DIRS) {
@@ -2274,11 +2366,38 @@ function runUpdateMode(projectDir, templatesDir) {
2274
2366
  if (existsSync12(settingsPath)) {
2275
2367
  report.staleHookRefs = cleanStaleHookRefs(settingsPath, join10(claudeDir, "hooks"));
2276
2368
  }
2369
+ const external = refreshExternalCli(projectDir, harnessRoot);
2370
+ report.externalUpdated = external.externalUpdated;
2371
+ report.externalBackedUp = external.externalBackedUp;
2277
2372
  return report;
2278
2373
  }
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
+ }
2279
2399
  function isHarnessOwned2(ctx, rel, current) {
2280
- const recorded = ctx.baseline.get(`${ctx.prefix}/${rel}`);
2281
- return recorded !== void 0 && recorded === hashContent(current);
2400
+ return isHarnessOwned(ctx.baseline, `${ctx.prefix}/${rel}`, current);
2282
2401
  }
2283
2402
  function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
2284
2403
  if (!existsSync12(target) || !existsSync12(source)) return { updated: 0, backedUp: [] };
@@ -2448,13 +2567,20 @@ function runInstall(ctx) {
2448
2567
  options: spec.options,
2449
2568
  ...spec.userOverride ? { userOverride: spec.userOverride } : {}
2450
2569
  }) ? installCiScaffold({ harnessRoot, projectDir, tracks: spec.tracks }) : null;
2451
- const { externalFiles, externalBackups, ...cliTransforms } = runCliTransforms(
2452
- spec,
2570
+ const {
2571
+ externalFiles,
2572
+ externalBackups,
2573
+ externalUpdated: _externalUpdated,
2574
+ externalBackedUp: _externalBackedUp,
2575
+ ...cliTransforms
2576
+ } = runCliTransforms({
2453
2577
  harnessRoot,
2454
2578
  projectDir,
2455
- manifestSpec.selectedInternalSkills,
2456
- previousLog?.externalFiles ?? []
2457
- );
2579
+ cli: spec.cli,
2580
+ selectedInternalSkills: manifestSpec.selectedInternalSkills,
2581
+ previousExternal: previousLog?.externalFiles ?? [],
2582
+ codexTrust: (spec.scope ?? "project") === "global" && spec.options.withCodexTrust
2583
+ });
2458
2584
  const baseline = {
2459
2585
  filesCopied: base.filesCopied,
2460
2586
  dirsCopied: base.dirsCopied,
@@ -2493,7 +2619,7 @@ function resolveBackupPath(ctx, mode, claudeDir) {
2493
2619
  return mode === "update" ? copyBackupDir(claudeDir) : backupDir(claudeDir);
2494
2620
  }
2495
2621
  function runUpdateInstall(ctx, templatesDir, backupPath) {
2496
- const updateReport = runUpdateMode(ctx.projectDir, templatesDir);
2622
+ const updateReport = runUpdateMode(ctx.projectDir, templatesDir, ctx.harnessRoot);
2497
2623
  const baseline = {
2498
2624
  filesCopied: 0,
2499
2625
  dirsCopied: 0,
@@ -2621,49 +2747,6 @@ function writeEnvironmentFiles(projectDir, tracks) {
2621
2747
  gitignoreNpxSkillsAdded: addGitignoreNpxSkillsAgents(projectDir)
2622
2748
  };
2623
2749
  }
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
- };
2635
- let codex = null;
2636
- let codexOptIn = null;
2637
- if (spec.cli.includes("codex")) {
2638
- codex = runCodexTransform({
2639
- harnessRoot,
2640
- projectDir,
2641
- selectedInternalSkills,
2642
- baseline
2643
- });
2644
- absorb(codex);
2645
- const installScope = spec.scope ?? "project";
2646
- if (installScope === "global" && spec.options.withCodexTrust) {
2647
- codexOptIn = runCodexOptIn({ projectDir });
2648
- }
2649
- }
2650
- let opencode = null;
2651
- if (spec.cli.includes("opencode")) {
2652
- opencode = runOpencodeTransform({ harnessRoot, projectDir, selectedInternalSkills, baseline });
2653
- absorb(opencode);
2654
- }
2655
- let antigravity = null;
2656
- if (spec.cli.includes("antigravity")) {
2657
- antigravity = runAntigravityTransform({
2658
- harnessRoot,
2659
- projectDir,
2660
- selectedInternalSkills,
2661
- baseline
2662
- });
2663
- absorb(antigravity);
2664
- }
2665
- return { codex, codexOptIn, opencode, antigravity, externalFiles, externalBackups };
2666
- }
2667
2750
  function runExternalPhase(ctx) {
2668
2751
  if (ctx.runExternal === null) {
2669
2752
  return null;
@@ -3190,6 +3273,24 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3190
3273
  )
3191
3274
  );
3192
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
+ }
3193
3294
  return;
3194
3295
  }
3195
3296
  if (baseline.backups) {