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