@uzysjung/agent-harness 26.133.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 +467 -366
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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: {
|
|
@@ -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/
|
|
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
|
|
966
|
-
import {
|
|
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
|
|
1062
|
-
import { dirname as dirname2, join as
|
|
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) =>
|
|
1228
|
+
return [...byPath.values()].filter((f) => existsSync3(join3(projectDir, f.path)));
|
|
1186
1229
|
}
|
|
1187
1230
|
function collectSkillHashes(projectDir) {
|
|
1188
|
-
const skillsDir =
|
|
1231
|
+
const skillsDir = join3(projectDir, ".claude/skills");
|
|
1189
1232
|
return listFilesRecursive(skillsDir).map((rel) => ({
|
|
1190
1233
|
path: rel,
|
|
1191
|
-
sha256: hashContent(readFileSync2(
|
|
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 =
|
|
1198
|
-
const sourceDir =
|
|
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 (!
|
|
1244
|
+
if (!existsSync3(join3(sourceDir, rel))) continue;
|
|
1202
1245
|
out.push({
|
|
1203
1246
|
path: `${dir}/${rel}`,
|
|
1204
|
-
sha256: hashContent(readFileSync2(
|
|
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 =
|
|
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 =
|
|
1219
|
-
if (!
|
|
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
|
|
1276
|
+
return join3(projectDir, ".claude", INSTALL_LOG_FILENAME);
|
|
1234
1277
|
}
|
|
1235
1278
|
|
|
1236
1279
|
// src/owned-write.ts
|
|
1237
|
-
function createOwnedWriter(projectDir, baseline,
|
|
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 (!
|
|
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 =
|
|
1370
|
-
if (!
|
|
1419
|
+
const src = join4(harnessRoot, "templates/skills", id, "SKILL.md");
|
|
1420
|
+
if (!existsSync5(src)) {
|
|
1371
1421
|
continue;
|
|
1372
1422
|
}
|
|
1373
|
-
const target =
|
|
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 =
|
|
1385
|
-
const templatePath =
|
|
1386
|
-
if (!
|
|
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 =
|
|
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
|
|
1625
|
-
if (!
|
|
1799
|
+
function readOptionalJson2(path) {
|
|
1800
|
+
if (!existsSync8(path)) {
|
|
1626
1801
|
return null;
|
|
1627
1802
|
}
|
|
1628
1803
|
try {
|
|
1629
|
-
return JSON.parse(
|
|
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
|
|
1638
|
-
import { join as
|
|
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 =
|
|
1676
|
-
if (
|
|
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 =
|
|
1684
|
-
if (!
|
|
1933
|
+
const path = join8(projectDir, ".gitignore");
|
|
1934
|
+
if (!existsSync9(path)) {
|
|
1685
1935
|
return false;
|
|
1686
1936
|
}
|
|
1687
|
-
const content =
|
|
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 =
|
|
1702
|
-
if (!
|
|
1951
|
+
const path = join8(projectDir, ".gitignore");
|
|
1952
|
+
if (!existsSync9(path)) {
|
|
1703
1953
|
return [];
|
|
1704
1954
|
}
|
|
1705
|
-
const content =
|
|
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 =
|
|
1722
|
-
if (
|
|
1971
|
+
const allowlistPath = join8(projectDir, ".mcp-allowlist");
|
|
1972
|
+
if (existsSync9(allowlistPath)) {
|
|
1723
1973
|
return null;
|
|
1724
1974
|
}
|
|
1725
|
-
const mcpPath =
|
|
1726
|
-
if (!
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
1833
|
-
if (!
|
|
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 =
|
|
1924
|
-
if (!
|
|
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 =
|
|
1932
|
-
if (!
|
|
1933
|
-
const parsed = JSON.parse(
|
|
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
|
|
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(
|
|
2012
|
-
const merged = opts.existingPath &&
|
|
2013
|
-
const mapRaw =
|
|
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(
|
|
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
|
-
|
|
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 {
|
|
2452
|
-
|
|
2570
|
+
const {
|
|
2571
|
+
externalFiles,
|
|
2572
|
+
externalBackups,
|
|
2573
|
+
externalUpdated: _externalUpdated,
|
|
2574
|
+
externalBackedUp: _externalBackedUp,
|
|
2575
|
+
...cliTransforms
|
|
2576
|
+
} = runCliTransforms({
|
|
2453
2577
|
harnessRoot,
|
|
2454
2578
|
projectDir,
|
|
2455
|
-
|
|
2456
|
-
|
|
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) {
|