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