@uzysjung/agent-harness 26.153.0 → 26.154.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/{chunk-OYNSK6QF.js → chunk-242TVGMZ.js} +558 -541
- package/dist/chunk-242TVGMZ.js.map +1 -0
- package/dist/index.js +577 -301
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-OYNSK6QF.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
CLI_BASES,
|
|
7
7
|
CONTINUOUS_SKILLS,
|
|
8
8
|
DEFAULT_OPTIONS,
|
|
9
|
-
DEV_METHOD_SKILL_IDS,
|
|
10
9
|
EXTERNAL_ASSETS,
|
|
11
10
|
HARNESS_ANCHOR_FILE,
|
|
12
11
|
HARNESS_IMPORT_LINE,
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
RETIRED_SKILL_IDS,
|
|
20
19
|
TRACKS,
|
|
21
20
|
TRACK_AGENTS,
|
|
21
|
+
UPDATE_GROUPS,
|
|
22
22
|
__commonJS,
|
|
23
23
|
__toESM,
|
|
24
24
|
anyTrack,
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
ensureDir,
|
|
40
40
|
ensureProjectSkeleton,
|
|
41
41
|
experimentalOptInCandidates,
|
|
42
|
+
extractFrontmatter,
|
|
42
43
|
filterApplicableAssets,
|
|
43
44
|
formatResidentCostLine,
|
|
44
45
|
hasUiTrack,
|
|
@@ -51,12 +52,14 @@ import {
|
|
|
51
52
|
isHarnessOwned,
|
|
52
53
|
isInstallScope,
|
|
53
54
|
isTrack,
|
|
55
|
+
landsOnDisk,
|
|
54
56
|
legacyInstallLogPath,
|
|
55
57
|
listFilesRecursive,
|
|
56
58
|
mergeExternalFiles,
|
|
57
59
|
readInstallLog,
|
|
58
60
|
renderFillScaffold,
|
|
59
61
|
residentCost,
|
|
62
|
+
resolveBundleRoot,
|
|
60
63
|
resolveRules,
|
|
61
64
|
resolveScope,
|
|
62
65
|
stripHarnessImport,
|
|
@@ -64,7 +67,7 @@ import {
|
|
|
64
67
|
upsertHarnessImport,
|
|
65
68
|
withContinuousSkillsNote,
|
|
66
69
|
writeInstallLog
|
|
67
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-242TVGMZ.js";
|
|
68
71
|
|
|
69
72
|
// node_modules/sisteransi/src/index.js
|
|
70
73
|
var require_src = __commonJS({
|
|
@@ -749,7 +752,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
749
752
|
// package.json
|
|
750
753
|
var package_default = {
|
|
751
754
|
name: "@uzysjung/agent-harness",
|
|
752
|
-
version: "26.
|
|
755
|
+
version: "26.154.0",
|
|
753
756
|
description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
|
|
754
757
|
type: "module",
|
|
755
758
|
publishConfig: {
|
|
@@ -814,6 +817,8 @@ import { fileURLToPath } from "url";
|
|
|
814
817
|
|
|
815
818
|
// src/baseline-targets.ts
|
|
816
819
|
init_esm_shims();
|
|
820
|
+
import { existsSync, readFileSync } from "fs";
|
|
821
|
+
import { join } from "path";
|
|
817
822
|
var BASELINE_KINDS = ["rules", "agents", "hooks", "skills"];
|
|
818
823
|
var BASELINE_PREFIX = "baseline:";
|
|
819
824
|
var PREFIX_BY_KIND = [
|
|
@@ -851,6 +856,42 @@ function isBaselineExcluded(target, excluded) {
|
|
|
851
856
|
const t = classifyBaselineTarget(target);
|
|
852
857
|
return t !== null && excluded.has(t.id);
|
|
853
858
|
}
|
|
859
|
+
var HINT_MAX = 96;
|
|
860
|
+
function clip(text) {
|
|
861
|
+
const t = text.replace(/\s+/g, " ").trim();
|
|
862
|
+
return t.length > HINT_MAX ? `${t.slice(0, HINT_MAX - 1)}\u2026` : t;
|
|
863
|
+
}
|
|
864
|
+
function describeBaselineTarget(t, templatesDir = join(resolveBundleRoot(), "templates")) {
|
|
865
|
+
const file = t.kind === "skills" ? join(templatesDir, "skills", t.name, "SKILL.md") : join(templatesDir, t.kind, `${t.name}.${t.kind === "hooks" ? "sh" : "md"}`);
|
|
866
|
+
if (!existsSync(file)) return void 0;
|
|
867
|
+
const content = readFileSync(file, "utf8");
|
|
868
|
+
if (t.kind === "agents" || t.kind === "skills") {
|
|
869
|
+
const fm = extractFrontmatter(content) ?? "";
|
|
870
|
+
const m = /^description:\s*(.+)$/m.exec(fm);
|
|
871
|
+
const raw = m?.[1]?.trim().replace(/^["']|["']$/g, "");
|
|
872
|
+
return raw ? clip(raw) : void 0;
|
|
873
|
+
}
|
|
874
|
+
if (t.kind === "hooks") {
|
|
875
|
+
const lines = content.split("\n").slice(1);
|
|
876
|
+
const comment = [];
|
|
877
|
+
for (const l of lines) {
|
|
878
|
+
if (!l.startsWith("#")) break;
|
|
879
|
+
comment.push(l.replace(/^#\s?/, ""));
|
|
880
|
+
}
|
|
881
|
+
return comment.length > 0 ? clip(comment.join(" \xB7 ")) : void 0;
|
|
882
|
+
}
|
|
883
|
+
const body = content.split("\n");
|
|
884
|
+
const heading = body.find((l) => l.startsWith("# "))?.slice(2).trim();
|
|
885
|
+
const first = body.slice(body.findIndex((l) => l.startsWith("# ")) + 1).find((l) => l.trim() !== "")?.replace(/^[-*]\s+/, "").replace(/\*\*/g, "");
|
|
886
|
+
if (!heading) return void 0;
|
|
887
|
+
return clip(first ? `${heading} \xB7 ${first}` : heading);
|
|
888
|
+
}
|
|
889
|
+
function withBaselineHints(targets, templatesDir) {
|
|
890
|
+
return targets.map((t) => {
|
|
891
|
+
const hint = describeBaselineTarget(t, templatesDir);
|
|
892
|
+
return hint ? { ...t, hint } : t;
|
|
893
|
+
});
|
|
894
|
+
}
|
|
854
895
|
|
|
855
896
|
// src/cli-targets.ts
|
|
856
897
|
init_esm_shims();
|
|
@@ -988,18 +1029,18 @@ function padDisplay(s, width) {
|
|
|
988
1029
|
init_esm_shims();
|
|
989
1030
|
import {
|
|
990
1031
|
chmodSync as chmodSync3,
|
|
991
|
-
existsSync as
|
|
1032
|
+
existsSync as existsSync14,
|
|
992
1033
|
mkdirSync as mkdirSync4,
|
|
993
1034
|
readdirSync as readdirSync4,
|
|
994
|
-
readFileSync as
|
|
1035
|
+
readFileSync as readFileSync13,
|
|
995
1036
|
writeFileSync as writeFileSync6
|
|
996
1037
|
} from "fs";
|
|
997
|
-
import { basename as basename5, dirname as dirname4, join as
|
|
1038
|
+
import { basename as basename5, dirname as dirname4, join as join13, resolve } from "path";
|
|
998
1039
|
|
|
999
1040
|
// src/ci-scaffold.ts
|
|
1000
1041
|
init_esm_shims();
|
|
1001
|
-
import { existsSync } from "fs";
|
|
1002
|
-
import { join } from "path";
|
|
1042
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1043
|
+
import { join as join2 } from "path";
|
|
1003
1044
|
var PYTHON_TRACKS = "data|csr-fastapi|full";
|
|
1004
1045
|
var VARIANTS = [
|
|
1005
1046
|
{
|
|
@@ -1023,12 +1064,12 @@ function installCiScaffold(ctx) {
|
|
|
1023
1064
|
for (const v2 of VARIANTS) {
|
|
1024
1065
|
if (!v2.applies(ctx.tracks)) continue;
|
|
1025
1066
|
const relTarget = `.github/workflows/${v2.target}`;
|
|
1026
|
-
const target =
|
|
1027
|
-
if (
|
|
1067
|
+
const target = join2(ctx.projectDir, relTarget);
|
|
1068
|
+
if (existsSync2(target)) {
|
|
1028
1069
|
report.skippedExisting.push(relTarget);
|
|
1029
1070
|
continue;
|
|
1030
1071
|
}
|
|
1031
|
-
copyFile(
|
|
1072
|
+
copyFile(join2(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
|
|
1032
1073
|
report.written.push(relTarget);
|
|
1033
1074
|
}
|
|
1034
1075
|
return report;
|
|
@@ -1039,8 +1080,8 @@ init_esm_shims();
|
|
|
1039
1080
|
|
|
1040
1081
|
// src/antigravity/transform.ts
|
|
1041
1082
|
init_esm_shims();
|
|
1042
|
-
import { existsSync as
|
|
1043
|
-
import { basename, join as
|
|
1083
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1084
|
+
import { basename, join as join6 } from "path";
|
|
1044
1085
|
|
|
1045
1086
|
// src/codex/agents-md.ts
|
|
1046
1087
|
init_esm_shims();
|
|
@@ -1055,8 +1096,8 @@ function renderAgentsMd(params) {
|
|
|
1055
1096
|
|
|
1056
1097
|
// src/codex/skills.ts
|
|
1057
1098
|
init_esm_shims();
|
|
1058
|
-
import { chmodSync, existsSync as
|
|
1059
|
-
import { join as
|
|
1099
|
+
import { chmodSync, existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1100
|
+
import { join as join3 } from "path";
|
|
1060
1101
|
function renderBundledSkill(source) {
|
|
1061
1102
|
const trimmed = source.trimEnd();
|
|
1062
1103
|
const lines = trimmed.split(/\r?\n/);
|
|
@@ -1088,14 +1129,14 @@ function writeBundledSkillDirs(params) {
|
|
|
1088
1129
|
const { harnessRoot, projectDir, skillIds, writer } = params;
|
|
1089
1130
|
const written = [];
|
|
1090
1131
|
for (const id of skillIds) {
|
|
1091
|
-
const srcDir =
|
|
1092
|
-
const srcSkill =
|
|
1093
|
-
if (!
|
|
1132
|
+
const srcDir = join3(harnessRoot, "templates/skills", id);
|
|
1133
|
+
const srcSkill = join3(srcDir, "SKILL.md");
|
|
1134
|
+
if (!existsSync3(srcSkill)) {
|
|
1094
1135
|
continue;
|
|
1095
1136
|
}
|
|
1096
|
-
const targetDir =
|
|
1097
|
-
const targetSkill =
|
|
1098
|
-
if (!writer.write(targetSkill, renderBundledSkill(
|
|
1137
|
+
const targetDir = join3(projectDir, ".agents", "skills", id);
|
|
1138
|
+
const targetSkill = join3(targetDir, "SKILL.md");
|
|
1139
|
+
if (!writer.write(targetSkill, renderBundledSkill(readFileSync2(srcSkill, "utf8")))) {
|
|
1099
1140
|
continue;
|
|
1100
1141
|
}
|
|
1101
1142
|
written.push(targetSkill);
|
|
@@ -1103,10 +1144,10 @@ function writeBundledSkillDirs(params) {
|
|
|
1103
1144
|
if (rel === "SKILL.md") continue;
|
|
1104
1145
|
if (rel.split("/").some((segment) => segment.startsWith("."))) continue;
|
|
1105
1146
|
const segments = rel.split("/");
|
|
1106
|
-
const raw =
|
|
1147
|
+
const raw = readFileSync2(join3(srcDir, ...segments), "utf8");
|
|
1107
1148
|
const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
|
|
1108
1149
|
` : raw;
|
|
1109
|
-
const target =
|
|
1150
|
+
const target = join3(targetDir, ...segments);
|
|
1110
1151
|
if (!writer.write(target, ported, { createInRefresh: true })) continue;
|
|
1111
1152
|
if (rel.endsWith(".sh")) chmodSync(target, 493);
|
|
1112
1153
|
written.push(target);
|
|
@@ -1117,13 +1158,13 @@ function writeBundledSkillDirs(params) {
|
|
|
1117
1158
|
|
|
1118
1159
|
// src/owned-write.ts
|
|
1119
1160
|
init_esm_shims();
|
|
1120
|
-
import { existsSync as
|
|
1161
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1121
1162
|
import { dirname, relative, sep } from "path";
|
|
1122
1163
|
|
|
1123
1164
|
// src/foreign-slot.ts
|
|
1124
1165
|
init_esm_shims();
|
|
1125
1166
|
import { lstatSync } from "fs";
|
|
1126
|
-
import { join as
|
|
1167
|
+
import { join as join4 } from "path";
|
|
1127
1168
|
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
1128
1169
|
function occupiedByNonDirectory(path) {
|
|
1129
1170
|
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
@@ -1132,15 +1173,15 @@ function occupiedByNonDirectory(path) {
|
|
|
1132
1173
|
function foreignOwnedTarget(projectDir, rel) {
|
|
1133
1174
|
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
1134
1175
|
if (slot === void 0) return null;
|
|
1135
|
-
if (occupiedByNonDirectory(
|
|
1176
|
+
if (occupiedByNonDirectory(join4(projectDir, slot))) return slot;
|
|
1136
1177
|
if (rel !== slot) {
|
|
1137
1178
|
const parts = rel.slice(slot.length + 1).split("/");
|
|
1138
1179
|
let cursor = slot;
|
|
1139
1180
|
for (const part of parts.slice(0, -1)) {
|
|
1140
1181
|
cursor = `${cursor}/${part}`;
|
|
1141
|
-
if (occupiedByNonDirectory(
|
|
1182
|
+
if (occupiedByNonDirectory(join4(projectDir, cursor))) return cursor;
|
|
1142
1183
|
}
|
|
1143
|
-
const leaf = lstatSync(
|
|
1184
|
+
const leaf = lstatSync(join4(projectDir, rel), { throwIfNoEntry: false });
|
|
1144
1185
|
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
1145
1186
|
}
|
|
1146
1187
|
return null;
|
|
@@ -1163,13 +1204,13 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1163
1204
|
return false;
|
|
1164
1205
|
}
|
|
1165
1206
|
const digest = hashContent(content);
|
|
1166
|
-
if (!
|
|
1207
|
+
if (!existsSync4(absPath)) {
|
|
1167
1208
|
if (refreshOnly && !opts?.createInRefresh) return false;
|
|
1168
1209
|
mkdirSync(dirname(absPath), { recursive: true });
|
|
1169
1210
|
writeFileSync(absPath, content);
|
|
1170
1211
|
updated++;
|
|
1171
1212
|
} else {
|
|
1172
|
-
const current =
|
|
1213
|
+
const current = readFileSync3(absPath, "utf8");
|
|
1173
1214
|
if (current !== content) {
|
|
1174
1215
|
if (!isHarnessOwned(baseline, rel, current)) {
|
|
1175
1216
|
backupPaths.push(backupFile(absPath, now));
|
|
@@ -1196,8 +1237,8 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1196
1237
|
|
|
1197
1238
|
// src/rules-port.ts
|
|
1198
1239
|
init_esm_shims();
|
|
1199
|
-
import { existsSync as
|
|
1200
|
-
import { join as
|
|
1240
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1241
|
+
import { join as join5 } from "path";
|
|
1201
1242
|
function stripClaudeFrontmatter(source) {
|
|
1202
1243
|
if (!source.startsWith("---\n")) return source.trim();
|
|
1203
1244
|
const end = source.indexOf("\n---", 4);
|
|
@@ -1207,9 +1248,9 @@ function stripClaudeFrontmatter(source) {
|
|
|
1207
1248
|
function portRules(harnessRoot, ruleNames) {
|
|
1208
1249
|
const ported = [];
|
|
1209
1250
|
for (const name of ruleNames) {
|
|
1210
|
-
const src =
|
|
1211
|
-
if (!
|
|
1212
|
-
ported.push({ name, body: stripClaudeFrontmatter(
|
|
1251
|
+
const src = join5(harnessRoot, "templates/rules", `${name}.md`);
|
|
1252
|
+
if (!existsSync5(src)) continue;
|
|
1253
|
+
ported.push({ name, body: stripClaudeFrontmatter(readFileSync4(src, "utf8")) });
|
|
1213
1254
|
}
|
|
1214
1255
|
return ported;
|
|
1215
1256
|
}
|
|
@@ -1233,7 +1274,7 @@ function runAntigravityTransform(params) {
|
|
|
1233
1274
|
const antigravityInstalled = rulesFile !== null;
|
|
1234
1275
|
const harnessRuleFiles = [];
|
|
1235
1276
|
for (const rule of portRules(harnessRoot, rules)) {
|
|
1236
|
-
const target =
|
|
1277
|
+
const target = join6(projectDir, ".agents", "rules", `${rule.name}.md`);
|
|
1237
1278
|
if (!writer.write(target, `${rule.body}
|
|
1238
1279
|
`, { createInRefresh: antigravityInstalled })) {
|
|
1239
1280
|
continue;
|
|
@@ -1254,14 +1295,14 @@ function runAntigravityTransform(params) {
|
|
|
1254
1295
|
};
|
|
1255
1296
|
}
|
|
1256
1297
|
function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills) {
|
|
1257
|
-
const claudeMdPath =
|
|
1258
|
-
const templatePath =
|
|
1259
|
-
if (!
|
|
1298
|
+
const claudeMdPath = join6(harnessRoot, "templates/CLAUDE.md");
|
|
1299
|
+
const templatePath = join6(harnessRoot, "templates/antigravity/AGENTS.md.template");
|
|
1300
|
+
if (!existsSync6(claudeMdPath) || !existsSync6(templatePath)) {
|
|
1260
1301
|
return null;
|
|
1261
1302
|
}
|
|
1262
|
-
const claudeMd =
|
|
1263
|
-
const template =
|
|
1264
|
-
const target =
|
|
1303
|
+
const claudeMd = readFileSync5(claudeMdPath, "utf8");
|
|
1304
|
+
const template = readFileSync5(templatePath, "utf8");
|
|
1305
|
+
const target = join6(projectDir, ".agents", "rules", "uzys-harness.md");
|
|
1265
1306
|
const rulesOut = renderAgentsMd({
|
|
1266
1307
|
template,
|
|
1267
1308
|
claudeMd,
|
|
@@ -1278,18 +1319,18 @@ function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills) {
|
|
|
1278
1319
|
// src/codex/opt-in.ts
|
|
1279
1320
|
init_esm_shims();
|
|
1280
1321
|
import { homedir } from "os";
|
|
1281
|
-
import { join as
|
|
1322
|
+
import { join as join7 } from "path";
|
|
1282
1323
|
|
|
1283
1324
|
// src/codex/trust-entry.ts
|
|
1284
1325
|
init_esm_shims();
|
|
1285
|
-
import { existsSync as
|
|
1326
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
1286
1327
|
import { dirname as dirname2 } from "path";
|
|
1287
1328
|
var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
|
|
1288
1329
|
function registerTrustEntry(opts) {
|
|
1289
1330
|
const { configPath, projectDir } = opts;
|
|
1290
1331
|
try {
|
|
1291
1332
|
mkdirSync2(dirname2(configPath), { recursive: true });
|
|
1292
|
-
const existing =
|
|
1333
|
+
const existing = existsSync7(configPath) ? readFileSync6(configPath, "utf8") : "";
|
|
1293
1334
|
if (hasTrustEntry(existing, projectDir)) {
|
|
1294
1335
|
return { status: "already-present" };
|
|
1295
1336
|
}
|
|
@@ -1313,8 +1354,8 @@ function hasTrustEntry(configContent, projectDir) {
|
|
|
1313
1354
|
|
|
1314
1355
|
// src/codex/opt-in.ts
|
|
1315
1356
|
function runCodexOptIn(ctx) {
|
|
1316
|
-
const codexHome = ctx.codexHome ??
|
|
1317
|
-
const configPath =
|
|
1357
|
+
const codexHome = ctx.codexHome ?? join7(homedir(), ".codex");
|
|
1358
|
+
const configPath = join7(codexHome, "config.toml");
|
|
1318
1359
|
const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
|
|
1319
1360
|
return {
|
|
1320
1361
|
trustEntry: {
|
|
@@ -1327,8 +1368,8 @@ function runCodexOptIn(ctx) {
|
|
|
1327
1368
|
|
|
1328
1369
|
// src/codex/transform.ts
|
|
1329
1370
|
init_esm_shims();
|
|
1330
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
1331
|
-
import { basename as basename2, join as
|
|
1371
|
+
import { chmodSync as chmodSync2, existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
1372
|
+
import { basename as basename2, join as join8 } from "path";
|
|
1332
1373
|
|
|
1333
1374
|
// src/codex/config-toml.ts
|
|
1334
1375
|
init_esm_shims();
|
|
@@ -1405,12 +1446,12 @@ function runCodexTransform(params) {
|
|
|
1405
1446
|
refreshOnly
|
|
1406
1447
|
} = params;
|
|
1407
1448
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1408
|
-
const claudeMd = readRequired(
|
|
1409
|
-
const agentsTemplate = readRequired(
|
|
1410
|
-
const configTemplate = readRequired(
|
|
1449
|
+
const claudeMd = readRequired(join8(harnessRoot, "templates/CLAUDE.md"));
|
|
1450
|
+
const agentsTemplate = readRequired(join8(harnessRoot, "templates/codex/AGENTS.md.template"));
|
|
1451
|
+
const configTemplate = readRequired(join8(harnessRoot, "templates/codex/config.toml.template"));
|
|
1411
1452
|
const projectName = basename2(projectDir);
|
|
1412
|
-
const mcp = readOptionalJson(
|
|
1413
|
-
const agentsMdPath =
|
|
1453
|
+
const mcp = readOptionalJson(join8(harnessRoot, ".mcp.json"));
|
|
1454
|
+
const agentsMdPath = join8(projectDir, "AGENTS.md");
|
|
1414
1455
|
ensureDir(projectDir);
|
|
1415
1456
|
const agentsMdOut = renderAgentsMd({
|
|
1416
1457
|
template: agentsTemplate,
|
|
@@ -1425,7 +1466,7 @@ function runCodexTransform(params) {
|
|
|
1425
1466
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1426
1467
|
});
|
|
1427
1468
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1428
|
-
const configTomlPath =
|
|
1469
|
+
const configTomlPath = join8(projectDir, ".codex/config.toml");
|
|
1429
1470
|
writer.write(
|
|
1430
1471
|
configTomlPath,
|
|
1431
1472
|
renderConfigToml({
|
|
@@ -1435,15 +1476,15 @@ function runCodexTransform(params) {
|
|
|
1435
1476
|
mcp
|
|
1436
1477
|
})
|
|
1437
1478
|
);
|
|
1438
|
-
const hookDir =
|
|
1479
|
+
const hookDir = join8(projectDir, ".codex/hooks");
|
|
1439
1480
|
const hookFiles = [];
|
|
1440
1481
|
for (const hook of HOOK_NAMES) {
|
|
1441
|
-
const src =
|
|
1442
|
-
if (!
|
|
1482
|
+
const src = join8(harnessRoot, "templates/hooks", `${hook}.sh`);
|
|
1483
|
+
if (!existsSync8(src)) {
|
|
1443
1484
|
continue;
|
|
1444
1485
|
}
|
|
1445
|
-
const ported =
|
|
1446
|
-
const target =
|
|
1486
|
+
const ported = readFileSync7(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
|
|
1487
|
+
const target = join8(hookDir, `${hook}.sh`);
|
|
1447
1488
|
if (!writer.write(target, ported)) continue;
|
|
1448
1489
|
chmodSync2(target, 493);
|
|
1449
1490
|
hookFiles.push(target);
|
|
@@ -1463,17 +1504,17 @@ function runCodexTransform(params) {
|
|
|
1463
1504
|
};
|
|
1464
1505
|
}
|
|
1465
1506
|
function readRequired(path) {
|
|
1466
|
-
if (!
|
|
1507
|
+
if (!existsSync8(path)) {
|
|
1467
1508
|
throw new Error(`Codex transform: required source missing: ${path}`);
|
|
1468
1509
|
}
|
|
1469
|
-
return
|
|
1510
|
+
return readFileSync7(path, "utf8");
|
|
1470
1511
|
}
|
|
1471
1512
|
function readOptionalJson(path) {
|
|
1472
|
-
if (!
|
|
1513
|
+
if (!existsSync8(path)) {
|
|
1473
1514
|
return null;
|
|
1474
1515
|
}
|
|
1475
1516
|
try {
|
|
1476
|
-
return JSON.parse(
|
|
1517
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1477
1518
|
} catch {
|
|
1478
1519
|
return null;
|
|
1479
1520
|
}
|
|
@@ -1481,8 +1522,8 @@ function readOptionalJson(path) {
|
|
|
1481
1522
|
|
|
1482
1523
|
// src/opencode/transform.ts
|
|
1483
1524
|
init_esm_shims();
|
|
1484
|
-
import { existsSync as
|
|
1485
|
-
import { basename as basename3, join as
|
|
1525
|
+
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync8, unlinkSync } from "fs";
|
|
1526
|
+
import { basename as basename3, join as join9 } from "path";
|
|
1486
1527
|
|
|
1487
1528
|
// src/opencode/agents-md.ts
|
|
1488
1529
|
init_esm_shims();
|
|
@@ -1525,15 +1566,15 @@ function runOpencodeTransform(params) {
|
|
|
1525
1566
|
refreshOnly
|
|
1526
1567
|
} = params;
|
|
1527
1568
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1528
|
-
const claudeMd = readRequired2(
|
|
1529
|
-
const agentsTemplate = readRequired2(
|
|
1569
|
+
const claudeMd = readRequired2(join9(harnessRoot, "templates/CLAUDE.md"));
|
|
1570
|
+
const agentsTemplate = readRequired2(join9(harnessRoot, "templates/opencode/AGENTS.md.template"));
|
|
1530
1571
|
const opencodeTemplate = readRequired2(
|
|
1531
|
-
|
|
1572
|
+
join9(harnessRoot, "templates/opencode/opencode.json.template")
|
|
1532
1573
|
);
|
|
1533
1574
|
const projectName = basename3(projectDir);
|
|
1534
|
-
const mcp = readOptionalJson2(
|
|
1575
|
+
const mcp = readOptionalJson2(join9(harnessRoot, ".mcp.json"));
|
|
1535
1576
|
ensureDir(projectDir);
|
|
1536
|
-
const agentsMdPath =
|
|
1577
|
+
const agentsMdPath = join9(projectDir, "AGENTS.md");
|
|
1537
1578
|
const agentsMdOut = renderAgentsMd2({
|
|
1538
1579
|
template: agentsTemplate,
|
|
1539
1580
|
claudeMd,
|
|
@@ -1549,7 +1590,7 @@ function runOpencodeTransform(params) {
|
|
|
1549
1590
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1550
1591
|
});
|
|
1551
1592
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1552
|
-
const opencodeJsonPath =
|
|
1593
|
+
const opencodeJsonPath = join9(projectDir, "opencode.json");
|
|
1553
1594
|
writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
|
|
1554
1595
|
const skillFiles = writeBundledSkillDirs({
|
|
1555
1596
|
harnessRoot,
|
|
@@ -1558,13 +1599,13 @@ function runOpencodeTransform(params) {
|
|
|
1558
1599
|
writer
|
|
1559
1600
|
});
|
|
1560
1601
|
const retiredCommands = [];
|
|
1561
|
-
const cmdDir =
|
|
1562
|
-
if (
|
|
1602
|
+
const cmdDir = join9(projectDir, ".opencode/commands");
|
|
1603
|
+
if (existsSync9(cmdDir)) {
|
|
1563
1604
|
for (const entry of readdirSync(cmdDir)) {
|
|
1564
1605
|
if (!entry.endsWith(".md")) continue;
|
|
1565
1606
|
const id = entry.slice(0, -3);
|
|
1566
|
-
if (!
|
|
1567
|
-
const victim =
|
|
1607
|
+
if (!existsSync9(join9(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
|
|
1608
|
+
const victim = join9(cmdDir, entry);
|
|
1568
1609
|
try {
|
|
1569
1610
|
backupFile(victim);
|
|
1570
1611
|
unlinkSync(victim);
|
|
@@ -1582,17 +1623,17 @@ function runOpencodeTransform(params) {
|
|
|
1582
1623
|
};
|
|
1583
1624
|
}
|
|
1584
1625
|
function readRequired2(path) {
|
|
1585
|
-
if (!
|
|
1626
|
+
if (!existsSync9(path)) {
|
|
1586
1627
|
throw new Error(`OpenCode transform: required source missing: ${path}`);
|
|
1587
1628
|
}
|
|
1588
|
-
return
|
|
1629
|
+
return readFileSync8(path, "utf8");
|
|
1589
1630
|
}
|
|
1590
1631
|
function readOptionalJson2(path) {
|
|
1591
|
-
if (!
|
|
1632
|
+
if (!existsSync9(path)) {
|
|
1592
1633
|
return null;
|
|
1593
1634
|
}
|
|
1594
1635
|
try {
|
|
1595
|
-
return JSON.parse(
|
|
1636
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
1596
1637
|
} catch {
|
|
1597
1638
|
return null;
|
|
1598
1639
|
}
|
|
@@ -1684,8 +1725,8 @@ function runCliTransforms(params) {
|
|
|
1684
1725
|
|
|
1685
1726
|
// src/env-files.ts
|
|
1686
1727
|
init_esm_shims();
|
|
1687
|
-
import { appendFileSync, existsSync as
|
|
1688
|
-
import { join as
|
|
1728
|
+
import { appendFileSync, existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
|
|
1729
|
+
import { join as join10 } from "path";
|
|
1689
1730
|
var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
|
|
1690
1731
|
# Copy to .env (gitignored) and fill in values: cp .env.example .env
|
|
1691
1732
|
|
|
@@ -1722,19 +1763,19 @@ function writeEnvExample(projectDir, tracks) {
|
|
|
1722
1763
|
if (!tracks.some((t) => ENV_EXAMPLE_TRACKS.includes(t))) {
|
|
1723
1764
|
return false;
|
|
1724
1765
|
}
|
|
1725
|
-
const path =
|
|
1726
|
-
if (
|
|
1766
|
+
const path = join10(projectDir, ".env.example");
|
|
1767
|
+
if (existsSync10(path)) {
|
|
1727
1768
|
return false;
|
|
1728
1769
|
}
|
|
1729
1770
|
writeFileSync3(path, ENV_EXAMPLE_BODY);
|
|
1730
1771
|
return true;
|
|
1731
1772
|
}
|
|
1732
1773
|
function addGitignoreEnv(projectDir) {
|
|
1733
|
-
const path =
|
|
1734
|
-
if (!
|
|
1774
|
+
const path = join10(projectDir, ".gitignore");
|
|
1775
|
+
if (!existsSync10(path)) {
|
|
1735
1776
|
return false;
|
|
1736
1777
|
}
|
|
1737
|
-
const content =
|
|
1778
|
+
const content = readFileSync9(path, "utf8");
|
|
1738
1779
|
if (GITIGNORE_ENV_PATTERN.test(content)) {
|
|
1739
1780
|
return false;
|
|
1740
1781
|
}
|
|
@@ -1748,11 +1789,11 @@ function addGitignoreEnv(projectDir) {
|
|
|
1748
1789
|
var AGENT_ARTIFACT_DIRS = [".factory/", ".goose/", ".uzys-agent-harness/"];
|
|
1749
1790
|
var GITIGNORE_AGENT_ARTIFACT_HEADER = "# agent CLI / harness \uC790\uB3D9 \uC0DD\uC131\uBB3C (auto-added by agent-harness)";
|
|
1750
1791
|
function addGitignoreAgentArtifacts(projectDir) {
|
|
1751
|
-
const path =
|
|
1752
|
-
if (!
|
|
1792
|
+
const path = join10(projectDir, ".gitignore");
|
|
1793
|
+
if (!existsSync10(path)) {
|
|
1753
1794
|
return [];
|
|
1754
1795
|
}
|
|
1755
|
-
const content =
|
|
1796
|
+
const content = readFileSync9(path, "utf8");
|
|
1756
1797
|
const missing = AGENT_ARTIFACT_DIRS.filter((pattern) => {
|
|
1757
1798
|
const lineRegex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\//g, "/")}\\s*$`, "m");
|
|
1758
1799
|
return !lineRegex.test(content);
|
|
@@ -1771,10 +1812,14 @@ ${block}
|
|
|
1771
1812
|
// src/external-installer.ts
|
|
1772
1813
|
init_esm_shims();
|
|
1773
1814
|
import { spawnSync } from "child_process";
|
|
1774
|
-
import { existsSync as
|
|
1815
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync10 } from "fs";
|
|
1775
1816
|
import { homedir as homedir2 } from "os";
|
|
1776
|
-
import { join as
|
|
1817
|
+
import { join as join11 } from "path";
|
|
1777
1818
|
var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
|
|
1819
|
+
var NPM_SPAWN_TIMEOUT_MS = 6e5;
|
|
1820
|
+
function spawnTimeoutFor(kind) {
|
|
1821
|
+
return kind === "npm" || kind === "npx-run" ? NPM_SPAWN_TIMEOUT_MS : DEFAULT_SPAWN_TIMEOUT_MS;
|
|
1822
|
+
}
|
|
1778
1823
|
function selectExternalTargets(assets, ctx) {
|
|
1779
1824
|
const conditionPassed = filterApplicableAssets(assets, ctx).filter(
|
|
1780
1825
|
(a) => a.method.kind !== "internal"
|
|
@@ -1849,8 +1894,8 @@ function installOne(asset, ctx) {
|
|
|
1849
1894
|
cwd
|
|
1850
1895
|
);
|
|
1851
1896
|
case "shell-script": {
|
|
1852
|
-
const scriptPath =
|
|
1853
|
-
if (!
|
|
1897
|
+
const scriptPath = join11(ctx.harnessRoot, method.script);
|
|
1898
|
+
if (!existsSync11(scriptPath)) {
|
|
1854
1899
|
return {
|
|
1855
1900
|
asset,
|
|
1856
1901
|
ok: false,
|
|
@@ -1946,8 +1991,17 @@ function installPlugin(asset, spawn, method, scope, cwd) {
|
|
|
1946
1991
|
);
|
|
1947
1992
|
}
|
|
1948
1993
|
function runSpawn(asset, spawn, cmd, args, cwd) {
|
|
1949
|
-
const
|
|
1994
|
+
const timeout = spawnTimeoutFor(asset.method.kind);
|
|
1995
|
+
const result = spawn(cmd, args, spawnOpts(cwd, timeout));
|
|
1950
1996
|
if (result.error) {
|
|
1997
|
+
const code = result.error.code;
|
|
1998
|
+
if (code === "ETIMEDOUT") {
|
|
1999
|
+
return {
|
|
2000
|
+
asset,
|
|
2001
|
+
ok: false,
|
|
2002
|
+
message: `timed out after ${Math.round(timeout / 1e3)}s \u2014 finish it yourself: ${cmd} ${args.join(" ")}`
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
1951
2005
|
return { asset, ok: false, message: result.error.message };
|
|
1952
2006
|
}
|
|
1953
2007
|
if ((result.status ?? 1) !== 0) {
|
|
@@ -1961,11 +2015,11 @@ function runSpawn(asset, spawn, cmd, args, cwd) {
|
|
|
1961
2015
|
}
|
|
1962
2016
|
return { asset, ok: true };
|
|
1963
2017
|
}
|
|
1964
|
-
function spawnOpts(cwd) {
|
|
2018
|
+
function spawnOpts(cwd, timeout = DEFAULT_SPAWN_TIMEOUT_MS) {
|
|
1965
2019
|
return {
|
|
1966
2020
|
encoding: "utf8",
|
|
1967
2021
|
stdio: "pipe",
|
|
1968
|
-
timeout
|
|
2022
|
+
timeout,
|
|
1969
2023
|
...cwd ? { cwd } : {}
|
|
1970
2024
|
};
|
|
1971
2025
|
}
|
|
@@ -1980,17 +2034,17 @@ function detectVersion(method, spawn) {
|
|
|
1980
2034
|
if (at2 <= 0) return void 0;
|
|
1981
2035
|
const plugin = method.pluginId.slice(0, at2);
|
|
1982
2036
|
const marketplaceShort = method.pluginId.slice(at2 + 1);
|
|
1983
|
-
const cacheBase =
|
|
1984
|
-
if (!
|
|
2037
|
+
const cacheBase = join11(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
|
|
2038
|
+
if (!existsSync11(cacheBase)) return void 0;
|
|
1985
2039
|
const versions = readdirSync2(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
|
|
1986
2040
|
return versions.at(-1);
|
|
1987
2041
|
}
|
|
1988
2042
|
case "npm": {
|
|
1989
2043
|
const npmRoot = getNpmGlobalRoot(spawn);
|
|
1990
2044
|
if (!npmRoot) return void 0;
|
|
1991
|
-
const pkgJson =
|
|
1992
|
-
if (!
|
|
1993
|
-
const parsed = JSON.parse(
|
|
2045
|
+
const pkgJson = join11(npmRoot, method.pkg, "package.json");
|
|
2046
|
+
if (!existsSync11(pkgJson)) return void 0;
|
|
2047
|
+
const parsed = JSON.parse(readFileSync10(pkgJson, "utf8"));
|
|
1994
2048
|
return parsed.version;
|
|
1995
2049
|
}
|
|
1996
2050
|
default:
|
|
@@ -2017,7 +2071,7 @@ function getNpmGlobalRoot(spawn) {
|
|
|
2017
2071
|
|
|
2018
2072
|
// src/mcp-merge.ts
|
|
2019
2073
|
init_esm_shims();
|
|
2020
|
-
import { existsSync as
|
|
2074
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
2021
2075
|
function parseTrackMcpMap(raw) {
|
|
2022
2076
|
const rows = [];
|
|
2023
2077
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -2068,15 +2122,15 @@ function mergeMcpServers(base, rows, tracks) {
|
|
|
2068
2122
|
return out;
|
|
2069
2123
|
}
|
|
2070
2124
|
function composeMcpJson(opts) {
|
|
2071
|
-
const base = JSON.parse(
|
|
2072
|
-
const merged = opts.existingPath &&
|
|
2073
|
-
const mapRaw =
|
|
2125
|
+
const base = JSON.parse(readFileSync11(opts.templateMcpPath, "utf8"));
|
|
2126
|
+
const merged = opts.existingPath && existsSync12(opts.existingPath) ? mergeUserBase(base, opts.existingPath) : base;
|
|
2127
|
+
const mapRaw = existsSync12(opts.trackMapPath) ? readFileSync11(opts.trackMapPath, "utf8") : "";
|
|
2074
2128
|
const rows = parseTrackMcpMap(mapRaw);
|
|
2075
2129
|
return mergeMcpServers(merged, rows, opts.tracks);
|
|
2076
2130
|
}
|
|
2077
2131
|
function mergeUserBase(base, existingPath) {
|
|
2078
2132
|
try {
|
|
2079
|
-
const existing = JSON.parse(
|
|
2133
|
+
const existing = JSON.parse(readFileSync11(existingPath, "utf8"));
|
|
2080
2134
|
return {
|
|
2081
2135
|
...base,
|
|
2082
2136
|
mcpServers: { ...base.mcpServers, ...existing.mcpServers }
|
|
@@ -2094,37 +2148,52 @@ function writeMcpJson(path, mcp) {
|
|
|
2094
2148
|
init_esm_shims();
|
|
2095
2149
|
import {
|
|
2096
2150
|
copyFileSync,
|
|
2097
|
-
existsSync as
|
|
2151
|
+
existsSync as existsSync13,
|
|
2098
2152
|
mkdirSync as mkdirSync3,
|
|
2099
2153
|
readdirSync as readdirSync3,
|
|
2100
|
-
readFileSync as
|
|
2154
|
+
readFileSync as readFileSync12,
|
|
2155
|
+
rmdirSync,
|
|
2156
|
+
statSync,
|
|
2101
2157
|
unlinkSync as unlinkSync2,
|
|
2102
2158
|
writeFileSync as writeFileSync5
|
|
2103
2159
|
} from "fs";
|
|
2104
|
-
import { basename as basename4, dirname as dirname3, join as
|
|
2160
|
+
import { basename as basename4, dirname as dirname3, join as join12 } from "path";
|
|
2105
2161
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
2106
|
-
function buildUpdateSpec(projectDir, tracks) {
|
|
2162
|
+
function buildUpdateSpec(projectDir, tracks, only) {
|
|
2107
2163
|
return {
|
|
2108
2164
|
tracks: [...tracks],
|
|
2109
2165
|
options: DEFAULT_OPTIONS,
|
|
2110
2166
|
cli: ["claude"],
|
|
2111
2167
|
projectDir,
|
|
2112
|
-
scope: readInstallLog(projectDir)?.scope ?? "project"
|
|
2168
|
+
scope: readInstallLog(projectDir)?.scope ?? "project",
|
|
2169
|
+
// 전부 골랐으면 "제한 없음"과 같다 — 화면·기록에 제한이 있었던 것처럼 남기지 않는다.
|
|
2170
|
+
...only !== void 0 && only.length > 0 && only.length < UPDATE_GROUPS.length ? { updateOnly: [...only] } : {}
|
|
2113
2171
|
};
|
|
2114
2172
|
}
|
|
2115
|
-
function
|
|
2116
|
-
const
|
|
2173
|
+
function parseUpdateOnly(raw) {
|
|
2174
|
+
const invalid = raw.filter((v2) => !UPDATE_GROUPS.includes(v2));
|
|
2175
|
+
if (invalid.length > 0) return { ok: false, invalid };
|
|
2176
|
+
return { ok: true, groups: [...new Set(raw)] };
|
|
2177
|
+
}
|
|
2178
|
+
function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
|
|
2179
|
+
const claudeDir = join12(projectDir, ".claude");
|
|
2180
|
+
const startedAt = Date.now();
|
|
2181
|
+
const wants = (g) => only === void 0 || only.includes(g);
|
|
2117
2182
|
const report = {
|
|
2118
2183
|
updated: {},
|
|
2119
2184
|
pruned: {},
|
|
2120
2185
|
staleHookRefs: [],
|
|
2121
2186
|
claudeMdUpdated: false,
|
|
2187
|
+
backups: [],
|
|
2188
|
+
skippedGroups: only === void 0 ? [] : UPDATE_GROUPS.filter((g) => !only.includes(g)),
|
|
2189
|
+
anchorBackedUp: false,
|
|
2122
2190
|
anchorCreated: false,
|
|
2123
2191
|
rootImportAdded: false,
|
|
2124
2192
|
rootBlockRefreshed: false,
|
|
2125
2193
|
legacyAnchor: null,
|
|
2126
2194
|
skillsBackedUp: [],
|
|
2127
2195
|
skillsSkippedLinks: [],
|
|
2196
|
+
skillsPruned: [],
|
|
2128
2197
|
policyBackedUp: [],
|
|
2129
2198
|
externalUpdated: 0,
|
|
2130
2199
|
externalBackedUp: [],
|
|
@@ -2140,14 +2209,15 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2140
2209
|
demotedAgents: [],
|
|
2141
2210
|
mcpAllowlistRetired: null
|
|
2142
2211
|
};
|
|
2143
|
-
const fresh = installNewAssets(projectDir, templatesDir);
|
|
2212
|
+
const fresh = installNewAssets(projectDir, templatesDir, wants);
|
|
2144
2213
|
report.installedNew = fresh.installed;
|
|
2145
2214
|
report.restored = fresh.restored;
|
|
2146
2215
|
report.needsReinstall = fresh.needsReinstall;
|
|
2147
2216
|
const policyBase = policyBaseline(projectDir);
|
|
2148
2217
|
for (const { dir, ext } of POLICY_DIRS) {
|
|
2149
|
-
|
|
2150
|
-
const
|
|
2218
|
+
if (!wants(dir === "hooks" ? "hooks" : "rules")) continue;
|
|
2219
|
+
const target = join12(claudeDir, dir);
|
|
2220
|
+
const source = join12(templatesDir, dir);
|
|
2151
2221
|
const label = `.claude/${dir}`;
|
|
2152
2222
|
const ctx = { prefix: dir, baseline: policyBase };
|
|
2153
2223
|
const synced = updateDir(target, source, ext, ctx);
|
|
@@ -2155,30 +2225,38 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2155
2225
|
report.policyBackedUp.push(...synced.backedUp);
|
|
2156
2226
|
report.pruned[label] = pruneOrphans(target, source, ext, ctx);
|
|
2157
2227
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2228
|
+
const syncedDirs = POLICY_DIRS.filter((d2) => wants(d2.dir === "hooks" ? "hooks" : "rules")).map(
|
|
2229
|
+
(d2) => d2.dir
|
|
2230
|
+
);
|
|
2231
|
+
if (syncedDirs.length > 0) refreshPolicyBaseline(projectDir, templatesDir, syncedDirs);
|
|
2232
|
+
const skillSync = wants("skills") ? syncSkills(
|
|
2233
|
+
join12(claudeDir, "skills"),
|
|
2234
|
+
join12(templatesDir, "skills"),
|
|
2162
2235
|
skillBaseline(projectDir),
|
|
2163
2236
|
/* @__PURE__ */ new Date(),
|
|
2164
2237
|
(relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`)
|
|
2165
|
-
);
|
|
2166
|
-
report.updated[".claude/skills"] = skillSync.updated;
|
|
2238
|
+
) : { updated: 0, backedUp: [], skippedLinks: [], foreignOwned: [], pruned: [] };
|
|
2239
|
+
if (wants("skills")) report.updated[".claude/skills"] = skillSync.updated;
|
|
2167
2240
|
report.skillsBackedUp = skillSync.backedUp;
|
|
2168
2241
|
report.skillsSkippedLinks = skillSync.skippedLinks;
|
|
2169
|
-
|
|
2242
|
+
report.skillsPruned = skillSync.pruned;
|
|
2243
|
+
const newSkills = wants("new-skills") ? installNewSkillDirs(projectDir, templatesDir, installedTracks(projectDir)) : { installed: [], foreignOwned: [] };
|
|
2244
|
+
report.installedNew.push(...newSkills.installed);
|
|
2245
|
+
if (wants("skills")) refreshSkillBaseline(projectDir, templatesDir);
|
|
2246
|
+
else if (newSkills.installed.length > 0)
|
|
2247
|
+
recordNewSkillBaseline(projectDir, templatesDir, newSkills.installed);
|
|
2170
2248
|
const installLog = readInstallLog(projectDir);
|
|
2171
|
-
if (
|
|
2172
|
-
syncHarnessAnchor(projectDir, templatesDir, report);
|
|
2249
|
+
if (existsSync13(claudeDir) && (installLog === null || installLog.spec.cli.includes("claude"))) {
|
|
2250
|
+
if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report);
|
|
2173
2251
|
}
|
|
2174
|
-
const settingsPath =
|
|
2175
|
-
if (
|
|
2252
|
+
const settingsPath = join12(claudeDir, "settings.json");
|
|
2253
|
+
if (wants("hooks") && existsSync13(settingsPath)) {
|
|
2176
2254
|
report.staleHookRefs = cleanStaleHookRefs(settingsPath, claudeDir);
|
|
2177
2255
|
}
|
|
2178
|
-
report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
2256
|
+
if (wants("hooks")) report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
2179
2257
|
report.retiredAgents = staleAgentFiles(claudeDir);
|
|
2180
2258
|
report.demotedAgents = demotedAgentFiles(claudeDir, installedTracks(projectDir));
|
|
2181
|
-
const external = refreshExternalCli(projectDir, harnessRoot);
|
|
2259
|
+
const external = wants("external") ? refreshExternalCli(projectDir, harnessRoot) : { externalUpdated: 0, externalBackedUp: [], externalForeignOwned: [] };
|
|
2182
2260
|
report.externalUpdated = external.externalUpdated;
|
|
2183
2261
|
report.externalBackedUp = external.externalBackedUp;
|
|
2184
2262
|
const saidBySlotRow = new Set(report.skillsSkippedLinks.map((id) => `.claude/skills/${id}`));
|
|
@@ -2186,12 +2264,13 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2186
2264
|
for (const f of [
|
|
2187
2265
|
...skillSync.foreignOwned,
|
|
2188
2266
|
...fresh.foreignOwned,
|
|
2267
|
+
...newSkills.foreignOwned,
|
|
2189
2268
|
...external.externalForeignOwned
|
|
2190
2269
|
]) {
|
|
2191
2270
|
if (!saidBySlotRow.has(f) && !merged.includes(f)) merged.push(f);
|
|
2192
2271
|
}
|
|
2193
2272
|
report.foreignOwned = merged;
|
|
2194
|
-
const skillRefresh = (deps.refreshSkills ?? refreshExternalSkills)(projectDir);
|
|
2273
|
+
const skillRefresh = wants("external") ? (deps.refreshSkills ?? refreshExternalSkills)(projectDir) : { attempted: 0, refreshed: 0, failed: [], notInCatalog: [], unknown: false };
|
|
2195
2274
|
report.externalSkillsRefreshed = skillRefresh.refreshed;
|
|
2196
2275
|
report.externalSkillsFailed = skillRefresh.failed;
|
|
2197
2276
|
report.externalSkillsNotInCatalog = [
|
|
@@ -2199,29 +2278,31 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2199
2278
|
...staleSkillDirs(claudeDir).filter((id) => !skillRefresh.notInCatalog.includes(id))
|
|
2200
2279
|
];
|
|
2201
2280
|
report.externalSkillsUnknown = skillRefresh.unknown;
|
|
2281
|
+
report.backups = collectRunBackups(projectDir, startedAt);
|
|
2282
|
+
writeBackupList(projectDir, report.backups);
|
|
2202
2283
|
return report;
|
|
2203
2284
|
}
|
|
2204
2285
|
function staleSkillDirs(claudeDir) {
|
|
2205
|
-
const dir =
|
|
2206
|
-
if (!
|
|
2286
|
+
const dir = join12(claudeDir, "skills");
|
|
2287
|
+
if (!existsSync13(dir)) return [];
|
|
2207
2288
|
const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
|
|
2208
2289
|
return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && stale(e2.name)).map((e2) => e2.name);
|
|
2209
2290
|
}
|
|
2210
2291
|
function staleAgentFiles(claudeDir) {
|
|
2211
|
-
const dir =
|
|
2212
|
-
if (!
|
|
2292
|
+
const dir = join12(claudeDir, "agents");
|
|
2293
|
+
if (!existsSync13(dir)) return [];
|
|
2213
2294
|
return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename4(e2.name, ".md")).filter((id) => RETIRED_AGENT_IDS.includes(id));
|
|
2214
2295
|
}
|
|
2215
2296
|
function demotedAgentFiles(claudeDir, tracks) {
|
|
2216
2297
|
if (tracks.length === 0) return [];
|
|
2217
|
-
const dir =
|
|
2218
|
-
if (!
|
|
2298
|
+
const dir = join12(claudeDir, "agents");
|
|
2299
|
+
if (!existsSync13(dir)) return [];
|
|
2219
2300
|
const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
|
|
2220
2301
|
([id]) => id
|
|
2221
2302
|
);
|
|
2222
2303
|
return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename4(e2.name, ".md")).filter((id) => demoted.includes(id));
|
|
2223
2304
|
}
|
|
2224
|
-
function installNewAssets(projectDir, templatesDir) {
|
|
2305
|
+
function installNewAssets(projectDir, templatesDir, wants = () => true) {
|
|
2225
2306
|
const installed = [];
|
|
2226
2307
|
const restored = [];
|
|
2227
2308
|
const needsReinstall = [];
|
|
@@ -2231,17 +2312,19 @@ function installNewAssets(projectDir, templatesDir) {
|
|
|
2231
2312
|
const baselineExcluded = new Set(log?.spec.baselineExclude ?? []);
|
|
2232
2313
|
const priorBaseline = policyBaseline(projectDir);
|
|
2233
2314
|
for (const entry of trackOnlyFileAssets(installedTracks(projectDir))) {
|
|
2315
|
+
const group = entry.target.startsWith(".claude/hooks/") || entry.target === ".claude/settings.json" ? "hooks" : "rules";
|
|
2316
|
+
if (!wants(group)) continue;
|
|
2234
2317
|
if (entry.target === HARNESS_ANCHOR_FILE) continue;
|
|
2235
2318
|
if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
|
|
2236
|
-
const target =
|
|
2319
|
+
const target = join12(projectDir, entry.target);
|
|
2237
2320
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
2238
2321
|
if (foreign !== null) {
|
|
2239
2322
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2240
2323
|
continue;
|
|
2241
2324
|
}
|
|
2242
|
-
if (
|
|
2243
|
-
const source =
|
|
2244
|
-
if (!
|
|
2325
|
+
if (existsSync13(target)) continue;
|
|
2326
|
+
const source = join12(templatesDir, entry.source);
|
|
2327
|
+
if (!existsSync13(source)) continue;
|
|
2245
2328
|
if (entry.target.startsWith(".claude/")) {
|
|
2246
2329
|
if (!claudeSelected) continue;
|
|
2247
2330
|
if (entry.target.startsWith(".claude/hooks/") || entry.target === ".claude/settings.json") {
|
|
@@ -2257,6 +2340,77 @@ function installNewAssets(projectDir, templatesDir) {
|
|
|
2257
2340
|
}
|
|
2258
2341
|
return { installed, restored, needsReinstall, foreignOwned };
|
|
2259
2342
|
}
|
|
2343
|
+
var BACKUP_SUFFIX = /\.backup-\d{8}T\d{6}(-\d+)?$/;
|
|
2344
|
+
var BACKUP_SCAN_DIRS = [".claude", ".codex", ".opencode", ".agents"];
|
|
2345
|
+
function collectRunBackups(projectDir, startedAt) {
|
|
2346
|
+
const out = [];
|
|
2347
|
+
const consider = (rel) => {
|
|
2348
|
+
if (!BACKUP_SUFFIX.test(rel)) return;
|
|
2349
|
+
const abs = join12(projectDir, rel);
|
|
2350
|
+
if (statSync(abs).mtimeMs < startedAt - 1e3) return;
|
|
2351
|
+
out.push({ path: rel.replace(BACKUP_SUFFIX, ""), backup: rel });
|
|
2352
|
+
};
|
|
2353
|
+
for (const e2 of readdirSync3(projectDir, { withFileTypes: true }))
|
|
2354
|
+
if (e2.isFile()) consider(e2.name);
|
|
2355
|
+
for (const dir of BACKUP_SCAN_DIRS) {
|
|
2356
|
+
for (const rel of listFilesRecursive(join12(projectDir, dir))) consider(`${dir}/${rel}`);
|
|
2357
|
+
}
|
|
2358
|
+
return out.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
2359
|
+
}
|
|
2360
|
+
function writeBackupList(projectDir, backups) {
|
|
2361
|
+
const dir = join12(projectDir, ".uzys-agent-harness");
|
|
2362
|
+
const file = join12(dir, "update-backups.json");
|
|
2363
|
+
try {
|
|
2364
|
+
if (backups.length === 0) {
|
|
2365
|
+
if (existsSync13(file)) unlinkSync2(file);
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
if (!existsSync13(dir)) return;
|
|
2369
|
+
writeFileSync5(
|
|
2370
|
+
file,
|
|
2371
|
+
`${JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString(), note: "update \uAC00 \uB0A8\uAE34 \uBC31\uC5C5 \uC30D. \uC6D0\uBCF8(path)\uC740 \uCD5C\uC2E0 \uBC88\uB4E4\uD310, \uBC31\uC5C5(backup)\uC740 \uB2F9\uC2E0\uC758 \uD3B8\uC9D1\uBD84. \uD3B8\uC9D1\uC744 \uC0C8 \uD310\uC5D0 \uB2E4\uC2DC \uC5B9\uC73C\uB824\uBA74 \uB450 \uD30C\uC77C\uC758 diff \uB97C \uBCF4\uB77C.", backups }, null, 2)}
|
|
2372
|
+
`
|
|
2373
|
+
);
|
|
2374
|
+
} catch {
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
function installNewSkillDirs(projectDir, templatesDir, tracks) {
|
|
2378
|
+
const installed = [];
|
|
2379
|
+
const foreignOwned = [];
|
|
2380
|
+
const log = readInstallLog(projectDir);
|
|
2381
|
+
const excluded = new Set(log?.spec.baselineExclude ?? []);
|
|
2382
|
+
if (!(log?.spec.cli ?? ["claude"]).includes("claude")) return { installed, foreignOwned };
|
|
2383
|
+
const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
|
|
2384
|
+
for (const entry of buildManifest(spec)) {
|
|
2385
|
+
if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
|
|
2386
|
+
if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
|
|
2387
|
+
const target = join12(projectDir, entry.target);
|
|
2388
|
+
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
2389
|
+
if (foreign !== null) {
|
|
2390
|
+
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2391
|
+
continue;
|
|
2392
|
+
}
|
|
2393
|
+
if (existsSync13(target)) continue;
|
|
2394
|
+
const source = join12(templatesDir, entry.source);
|
|
2395
|
+
if (!existsSync13(source)) continue;
|
|
2396
|
+
copyDir(source, target, (rel) => foreignOwnedTarget(projectDir, `${entry.target}/${rel}`));
|
|
2397
|
+
installed.push(entry.target);
|
|
2398
|
+
}
|
|
2399
|
+
return { installed, foreignOwned };
|
|
2400
|
+
}
|
|
2401
|
+
function recordNewSkillBaseline(projectDir, templatesDir, targets) {
|
|
2402
|
+
const log = readInstallLog(projectDir);
|
|
2403
|
+
if (!log) return;
|
|
2404
|
+
const ids = new Set(targets.map((t) => t.slice(".claude/skills/".length)));
|
|
2405
|
+
const added = collectSkillHashes(projectDir, templatesDir).filter(
|
|
2406
|
+
(f) => ids.has(f.path.split("/")[0] ?? "")
|
|
2407
|
+
);
|
|
2408
|
+
const kept = (log.skillFiles ?? []).filter((f) => !ids.has(f.path.split("/")[0] ?? ""));
|
|
2409
|
+
try {
|
|
2410
|
+
writeInstallLog(projectDir, { ...log, skillFiles: [...kept, ...added] });
|
|
2411
|
+
} catch {
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2260
2414
|
function trackOnlyFileAssets(tracks) {
|
|
2261
2415
|
const base = { tracks, selectedInternalSkills: [] };
|
|
2262
2416
|
const optedOut = { ...base, withEcc: false, withTauri: false };
|
|
@@ -2265,34 +2419,45 @@ function trackOnlyFileAssets(tracks) {
|
|
|
2265
2419
|
(e2) => e2.type === "file" && e2.applies(optedOut) && e2.applies(optedIn)
|
|
2266
2420
|
);
|
|
2267
2421
|
}
|
|
2268
|
-
function syncHarnessAnchor(projectDir, templatesDir, report) {
|
|
2269
|
-
if (
|
|
2422
|
+
function syncHarnessAnchor(projectDir, templatesDir, report, now = /* @__PURE__ */ new Date()) {
|
|
2423
|
+
if (existsSync13(join12(projectDir, LEGACY_ANCHOR_FILE))) {
|
|
2270
2424
|
report.legacyAnchor = LEGACY_ANCHOR_FILE;
|
|
2271
2425
|
}
|
|
2272
|
-
const templateMd =
|
|
2273
|
-
if (!
|
|
2274
|
-
const anchor =
|
|
2275
|
-
const existed =
|
|
2276
|
-
copyFileSync(templateMd, anchor);
|
|
2426
|
+
const templateMd = join12(templatesDir, "CLAUDE.md");
|
|
2427
|
+
if (!existsSync13(templateMd)) return;
|
|
2428
|
+
const anchor = join12(projectDir, HARNESS_ANCHOR_FILE);
|
|
2429
|
+
const existed = existsSync13(anchor);
|
|
2277
2430
|
if (existed) {
|
|
2431
|
+
const current = readFileSync12(anchor, "utf8");
|
|
2432
|
+
if (current !== readFileSync12(templateMd, "utf8")) {
|
|
2433
|
+
const recorded = readInstallLog(projectDir)?.templates.rootClaudeMd;
|
|
2434
|
+
const owned = recorded?.path === HARNESS_ANCHOR_FILE && hashContent(current) === recorded.sha256;
|
|
2435
|
+
if (!owned) {
|
|
2436
|
+
backupFile(anchor, now);
|
|
2437
|
+
report.anchorBackedUp = true;
|
|
2438
|
+
}
|
|
2439
|
+
copyFileSync(templateMd, anchor);
|
|
2440
|
+
}
|
|
2278
2441
|
report.claudeMdUpdated = true;
|
|
2442
|
+
recordAnchorBaseline(projectDir, anchor);
|
|
2279
2443
|
report.rootBlockRefreshed = upsertRootImport(projectDir);
|
|
2280
2444
|
return;
|
|
2281
2445
|
}
|
|
2446
|
+
copyFileSync(templateMd, anchor);
|
|
2282
2447
|
report.anchorCreated = true;
|
|
2283
2448
|
report.rootImportAdded = upsertRootImport(projectDir);
|
|
2284
2449
|
recordAnchorBaseline(projectDir, anchor);
|
|
2285
2450
|
}
|
|
2286
2451
|
function upsertRootImport(projectDir) {
|
|
2287
|
-
const target =
|
|
2288
|
-
const existing =
|
|
2452
|
+
const target = join12(projectDir, "CLAUDE.md");
|
|
2453
|
+
const existing = existsSync13(target) ? readFileSync12(target, "utf8") : null;
|
|
2289
2454
|
const next = upsertHarnessImport(existing, {
|
|
2290
2455
|
projectName: basename4(projectDir),
|
|
2291
2456
|
tracks: installedTracks(projectDir),
|
|
2292
2457
|
// ADR-085 — update 는 선택 목록을 인자로 받지 않는다. `syncSkills` 와 같은 판정을 쓴다:
|
|
2293
2458
|
// `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
|
|
2294
2459
|
continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
|
|
2295
|
-
(id) =>
|
|
2460
|
+
(id) => existsSync13(join12(projectDir, ".claude/skills", id))
|
|
2296
2461
|
)
|
|
2297
2462
|
});
|
|
2298
2463
|
if (next === existing) return false;
|
|
@@ -2312,7 +2477,7 @@ function recordAnchorBaseline(projectDir, anchor) {
|
|
|
2312
2477
|
...log.templates,
|
|
2313
2478
|
rootClaudeMd: {
|
|
2314
2479
|
path: HARNESS_ANCHOR_FILE,
|
|
2315
|
-
sha256: hashContent(
|
|
2480
|
+
sha256: hashContent(readFileSync12(anchor, "utf8"))
|
|
2316
2481
|
}
|
|
2317
2482
|
}
|
|
2318
2483
|
};
|
|
@@ -2354,16 +2519,16 @@ function isHarnessOwned2(ctx, rel, current) {
|
|
|
2354
2519
|
return isHarnessOwned(ctx.baseline, `${ctx.prefix}/${rel}`, current);
|
|
2355
2520
|
}
|
|
2356
2521
|
function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
2357
|
-
if (!
|
|
2522
|
+
if (!existsSync13(target) || !existsSync13(source)) return { updated: 0, backedUp: [] };
|
|
2358
2523
|
let updated = 0;
|
|
2359
2524
|
const backedUp = [];
|
|
2360
2525
|
for (const file of readdirSync3(target)) {
|
|
2361
2526
|
if (!file.endsWith(ext)) continue;
|
|
2362
|
-
const targetFile =
|
|
2363
|
-
const sourceFile =
|
|
2364
|
-
if (!
|
|
2365
|
-
const next =
|
|
2366
|
-
const current =
|
|
2527
|
+
const targetFile = join12(target, file);
|
|
2528
|
+
const sourceFile = join12(source, file);
|
|
2529
|
+
if (!existsSync13(sourceFile)) continue;
|
|
2530
|
+
const next = readFileSync12(sourceFile, "utf8");
|
|
2531
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2367
2532
|
if (current === next) continue;
|
|
2368
2533
|
if (!isHarnessOwned2(ctx, file, current)) {
|
|
2369
2534
|
backupFile(targetFile, now);
|
|
@@ -2375,35 +2540,36 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
|
2375
2540
|
return { updated, backedUp };
|
|
2376
2541
|
}
|
|
2377
2542
|
function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date(), foreignOf) {
|
|
2378
|
-
if (!
|
|
2379
|
-
return { updated: 0, backedUp: [], skippedLinks: [], foreignOwned: [] };
|
|
2543
|
+
if (!existsSync13(targetDir) || !existsSync13(sourceDir))
|
|
2544
|
+
return { updated: 0, backedUp: [], skippedLinks: [], foreignOwned: [], pruned: [] };
|
|
2380
2545
|
let updated = 0;
|
|
2381
2546
|
const backedUp = [];
|
|
2382
2547
|
const skippedLinks = [];
|
|
2383
2548
|
const foreignOwned = [];
|
|
2549
|
+
const pruned = [];
|
|
2384
2550
|
for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
2385
2551
|
if (!skill.isDirectory()) continue;
|
|
2386
|
-
const targetSkill =
|
|
2552
|
+
const targetSkill = join12(targetDir, skill.name);
|
|
2387
2553
|
if (occupiedByNonDirectory(targetSkill)) {
|
|
2388
2554
|
skippedLinks.push(skill.name);
|
|
2389
2555
|
continue;
|
|
2390
2556
|
}
|
|
2391
|
-
if (!
|
|
2392
|
-
for (const rel of listFilesRecursive(
|
|
2393
|
-
const targetFile =
|
|
2557
|
+
if (!existsSync13(targetSkill)) continue;
|
|
2558
|
+
for (const rel of listFilesRecursive(join12(sourceDir, skill.name))) {
|
|
2559
|
+
const targetFile = join12(targetSkill, rel);
|
|
2394
2560
|
const foreign = foreignOf(`${skill.name}/${rel}`);
|
|
2395
2561
|
if (foreign !== null) {
|
|
2396
2562
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2397
2563
|
continue;
|
|
2398
2564
|
}
|
|
2399
|
-
const next =
|
|
2400
|
-
if (!
|
|
2565
|
+
const next = readFileSync12(join12(sourceDir, skill.name, rel), "utf8");
|
|
2566
|
+
if (!existsSync13(targetFile)) {
|
|
2401
2567
|
mkdirSync3(dirname3(targetFile), { recursive: true });
|
|
2402
2568
|
writeFileSync5(targetFile, next);
|
|
2403
2569
|
updated++;
|
|
2404
2570
|
continue;
|
|
2405
2571
|
}
|
|
2406
|
-
const current =
|
|
2572
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2407
2573
|
if (current === next) continue;
|
|
2408
2574
|
const recorded = baseline.get(`${skill.name}/${rel}`);
|
|
2409
2575
|
if (recorded === void 0 || hashContent(current) !== recorded) {
|
|
@@ -2413,8 +2579,23 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
2413
2579
|
writeFileSync5(targetFile, next);
|
|
2414
2580
|
updated++;
|
|
2415
2581
|
}
|
|
2582
|
+
const bundled = new Set(listFilesRecursive(join12(sourceDir, skill.name)));
|
|
2583
|
+
for (const rel of listFilesRecursive(targetSkill)) {
|
|
2584
|
+
if (bundled.has(rel) || basename4(rel).includes(".backup-")) continue;
|
|
2585
|
+
if (foreignOf(`${skill.name}/${rel}`) !== null) continue;
|
|
2586
|
+
const targetFile = join12(targetSkill, rel);
|
|
2587
|
+
const recorded = baseline.get(`${skill.name}/${rel}`);
|
|
2588
|
+
if (recorded === void 0 || hashContent(readFileSync12(targetFile, "utf8")) !== recorded) {
|
|
2589
|
+
backupFile(targetFile, now);
|
|
2590
|
+
backedUp.push(`${skill.name}/${rel}`);
|
|
2591
|
+
}
|
|
2592
|
+
unlinkSync2(targetFile);
|
|
2593
|
+
pruned.push(`${skill.name}/${rel}`);
|
|
2594
|
+
for (let d2 = dirname3(targetFile); d2 !== targetSkill && readdirSync3(d2).length === 0; d2 = dirname3(d2))
|
|
2595
|
+
rmdirSync(d2);
|
|
2596
|
+
}
|
|
2416
2597
|
}
|
|
2417
|
-
return { updated, backedUp, skippedLinks, foreignOwned };
|
|
2598
|
+
return { updated, backedUp, skippedLinks, foreignOwned, pruned };
|
|
2418
2599
|
}
|
|
2419
2600
|
function skillBaseline(projectDir) {
|
|
2420
2601
|
const log = readInstallLog(projectDir);
|
|
@@ -2424,10 +2605,14 @@ function policyBaseline(projectDir) {
|
|
|
2424
2605
|
const log = readInstallLog(projectDir);
|
|
2425
2606
|
return new Map((log?.policyFiles ?? []).map((f) => [f.path, f.sha256]));
|
|
2426
2607
|
}
|
|
2427
|
-
function refreshPolicyBaseline(projectDir, templatesDir) {
|
|
2608
|
+
function refreshPolicyBaseline(projectDir, templatesDir, dirs) {
|
|
2428
2609
|
const log = readInstallLog(projectDir);
|
|
2429
2610
|
if (!log) return;
|
|
2430
|
-
const
|
|
2611
|
+
const inDirs = (path) => dirs === void 0 || dirs.some((d2) => path.startsWith(`${d2}/`));
|
|
2612
|
+
const policyFiles = [
|
|
2613
|
+
...(log.policyFiles ?? []).filter((f) => !inDirs(f.path)),
|
|
2614
|
+
...collectPolicyHashes(projectDir, templatesDir).filter((f) => inDirs(f.path))
|
|
2615
|
+
];
|
|
2431
2616
|
const next = { ...log };
|
|
2432
2617
|
if (policyFiles.length > 0) next.policyFiles = policyFiles;
|
|
2433
2618
|
else delete next.policyFiles;
|
|
@@ -2436,10 +2621,10 @@ function refreshPolicyBaseline(projectDir, templatesDir) {
|
|
|
2436
2621
|
} catch {
|
|
2437
2622
|
}
|
|
2438
2623
|
}
|
|
2439
|
-
function refreshSkillBaseline(projectDir) {
|
|
2624
|
+
function refreshSkillBaseline(projectDir, templatesDir) {
|
|
2440
2625
|
const log = readInstallLog(projectDir);
|
|
2441
2626
|
if (!log) return;
|
|
2442
|
-
const skillFiles = collectSkillHashes(projectDir);
|
|
2627
|
+
const skillFiles = collectSkillHashes(projectDir, templatesDir);
|
|
2443
2628
|
const next = { ...log };
|
|
2444
2629
|
if (skillFiles.length > 0) next.skillFiles = skillFiles;
|
|
2445
2630
|
else delete next.skillFiles;
|
|
@@ -2449,16 +2634,16 @@ function refreshSkillBaseline(projectDir) {
|
|
|
2449
2634
|
}
|
|
2450
2635
|
}
|
|
2451
2636
|
function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
2452
|
-
if (!
|
|
2637
|
+
if (!existsSync13(target) || !existsSync13(source)) return [];
|
|
2453
2638
|
const removed = [];
|
|
2454
2639
|
for (const file of readdirSync3(target)) {
|
|
2455
2640
|
if (!file.endsWith(ext)) continue;
|
|
2456
|
-
const sourceFile =
|
|
2457
|
-
if (!
|
|
2458
|
-
const targetFile =
|
|
2641
|
+
const sourceFile = join12(source, file);
|
|
2642
|
+
if (!existsSync13(sourceFile)) {
|
|
2643
|
+
const targetFile = join12(target, file);
|
|
2459
2644
|
if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
|
|
2460
2645
|
try {
|
|
2461
|
-
const current =
|
|
2646
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2462
2647
|
if (!isHarnessOwned2(ctx, file, current)) backupFile(targetFile, now);
|
|
2463
2648
|
unlinkSync2(targetFile);
|
|
2464
2649
|
removed.push(file);
|
|
@@ -2469,8 +2654,8 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
2469
2654
|
return removed;
|
|
2470
2655
|
}
|
|
2471
2656
|
function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
2472
|
-
const target =
|
|
2473
|
-
if (!
|
|
2657
|
+
const target = join12(projectDir, ".mcp-allowlist");
|
|
2658
|
+
if (!existsSync13(target)) return null;
|
|
2474
2659
|
try {
|
|
2475
2660
|
const backup = backupFile(target, now);
|
|
2476
2661
|
unlinkSync2(target);
|
|
@@ -2482,7 +2667,7 @@ function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
|
2482
2667
|
function cleanStaleHookRefs(settingsPath, claudeDir) {
|
|
2483
2668
|
let settings;
|
|
2484
2669
|
try {
|
|
2485
|
-
settings = JSON.parse(
|
|
2670
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
2486
2671
|
} catch {
|
|
2487
2672
|
return [];
|
|
2488
2673
|
}
|
|
@@ -2509,7 +2694,7 @@ function cleanStaleHookRefs(settingsPath, claudeDir) {
|
|
|
2509
2694
|
function keepHookRef(hook, claudeDir, removed) {
|
|
2510
2695
|
const relPath = projectAnchoredRef(hook?.command ?? "", claudeDir);
|
|
2511
2696
|
if (relPath === void 0) return true;
|
|
2512
|
-
const exists =
|
|
2697
|
+
const exists = existsSync13(join12(claudeDir, relPath));
|
|
2513
2698
|
if (!exists && !removed.includes(relPath)) removed.push(relPath);
|
|
2514
2699
|
return exists;
|
|
2515
2700
|
}
|
|
@@ -2536,14 +2721,14 @@ function projectAnchoredRef(command, claudeDir) {
|
|
|
2536
2721
|
function runInstall(ctx) {
|
|
2537
2722
|
const { harnessRoot, projectDir, spec } = ctx;
|
|
2538
2723
|
const mode = ctx.mode ?? "fresh";
|
|
2539
|
-
const templatesDir =
|
|
2540
|
-
if (!
|
|
2724
|
+
const templatesDir = join13(harnessRoot, "templates");
|
|
2725
|
+
if (!existsSync14(templatesDir)) {
|
|
2541
2726
|
throw new Error(`Templates dir not found: ${templatesDir}`);
|
|
2542
2727
|
}
|
|
2543
|
-
const claudeDir =
|
|
2728
|
+
const claudeDir = join13(projectDir, ".claude");
|
|
2544
2729
|
const previousLog = readInstallLog(projectDir);
|
|
2545
2730
|
const claudeWasSelected = previousLog?.spec.cli.includes("claude") ?? false;
|
|
2546
|
-
if (mode === "update" && !
|
|
2731
|
+
if (mode === "update" && !existsSync14(claudeDir) && (previousLog === null || claudeWasSelected)) {
|
|
2547
2732
|
throw new Error(
|
|
2548
2733
|
claudeWasSelected ? `Update mode found a broken install at ${projectDir} \u2014 this project installed Claude Code assets but \`.claude/\` is gone. Reinstall instead: agent-harness install --track <name>` : `Update mode requires an existing install at ${projectDir}`
|
|
2549
2734
|
);
|
|
@@ -2628,9 +2813,9 @@ function runInstall(ctx) {
|
|
|
2628
2813
|
}
|
|
2629
2814
|
function healStaleHookRefs(spec, projectDir) {
|
|
2630
2815
|
if (!spec.cli.includes("claude")) return [];
|
|
2631
|
-
const settingsPath =
|
|
2632
|
-
if (!
|
|
2633
|
-
return cleanStaleHookRefs(settingsPath,
|
|
2816
|
+
const settingsPath = join13(projectDir, ".claude/settings.json");
|
|
2817
|
+
if (!existsSync14(settingsPath)) return [];
|
|
2818
|
+
return cleanStaleHookRefs(settingsPath, join13(projectDir, ".claude"));
|
|
2634
2819
|
}
|
|
2635
2820
|
function resolveBackupPath(ctx, mode, claudeDir) {
|
|
2636
2821
|
const wantBackup = ctx.backup ?? (mode === "update" || mode === "reinstall");
|
|
@@ -2638,7 +2823,13 @@ function resolveBackupPath(ctx, mode, claudeDir) {
|
|
|
2638
2823
|
return mode === "update" ? copyBackupDir(claudeDir) : backupDir(claudeDir);
|
|
2639
2824
|
}
|
|
2640
2825
|
function runUpdateInstall(ctx, templatesDir, backupPath) {
|
|
2641
|
-
const updateReport = runUpdateMode(
|
|
2826
|
+
const updateReport = runUpdateMode(
|
|
2827
|
+
ctx.projectDir,
|
|
2828
|
+
templatesDir,
|
|
2829
|
+
ctx.harnessRoot,
|
|
2830
|
+
{},
|
|
2831
|
+
ctx.spec.updateOnly
|
|
2832
|
+
);
|
|
2642
2833
|
const baseline = {
|
|
2643
2834
|
filesCopied: 0,
|
|
2644
2835
|
dirsCopied: 0,
|
|
@@ -2696,12 +2887,12 @@ function installCliNeutralAssets(manifestSpec, projectDir, templatesDir, baselin
|
|
|
2696
2887
|
continue;
|
|
2697
2888
|
}
|
|
2698
2889
|
if (!isCliNeutralTarget(entry.target)) continue;
|
|
2699
|
-
const source =
|
|
2700
|
-
if (!
|
|
2890
|
+
const source = join13(templatesDir, entry.source);
|
|
2891
|
+
if (!existsSync14(source)) {
|
|
2701
2892
|
result.skipped += 1;
|
|
2702
2893
|
continue;
|
|
2703
2894
|
}
|
|
2704
|
-
copyFile(source,
|
|
2895
|
+
copyFile(source, join13(projectDir, entry.target));
|
|
2705
2896
|
result.filesCopied += 1;
|
|
2706
2897
|
}
|
|
2707
2898
|
return result;
|
|
@@ -2713,9 +2904,9 @@ function backupEditedPolicyFile(entryTarget, target, source, baseline) {
|
|
|
2713
2904
|
if (!POLICY_DIRS.some(({ dir, ext }) => rel.startsWith(`${dir}/`) && rel.endsWith(ext))) {
|
|
2714
2905
|
return null;
|
|
2715
2906
|
}
|
|
2716
|
-
if (!
|
|
2717
|
-
const current =
|
|
2718
|
-
if (current ===
|
|
2907
|
+
if (!existsSync14(target)) return null;
|
|
2908
|
+
const current = readFileSync13(target, "utf-8");
|
|
2909
|
+
if (current === readFileSync13(source, "utf-8")) return null;
|
|
2719
2910
|
const recorded = baseline.get(rel);
|
|
2720
2911
|
if (recorded !== void 0 && recorded === hashContent(current)) return null;
|
|
2721
2912
|
return backupFile(target);
|
|
@@ -2728,14 +2919,14 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2728
2919
|
if (!entry.applies(manifestSpec)) {
|
|
2729
2920
|
continue;
|
|
2730
2921
|
}
|
|
2731
|
-
const target =
|
|
2922
|
+
const target = join13(projectDir, entry.target);
|
|
2732
2923
|
if (isBaselineExcluded(entry.target, baselineExcluded)) {
|
|
2733
2924
|
result.excluded.push(entry.target);
|
|
2734
|
-
if (
|
|
2925
|
+
if (existsSync14(target)) result.excludedOnDisk.push(entry.target);
|
|
2735
2926
|
continue;
|
|
2736
2927
|
}
|
|
2737
|
-
const source =
|
|
2738
|
-
if (!
|
|
2928
|
+
const source = join13(templatesDir, entry.source);
|
|
2929
|
+
if (!existsSync14(source)) {
|
|
2739
2930
|
result.skipped += 1;
|
|
2740
2931
|
continue;
|
|
2741
2932
|
}
|
|
@@ -2746,7 +2937,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2746
2937
|
}
|
|
2747
2938
|
if (entry.type === "file") {
|
|
2748
2939
|
if (entry.target === ".claude/settings.json") {
|
|
2749
|
-
const backup = backupFileIfChanged(target,
|
|
2940
|
+
const backup = backupFileIfChanged(target, readFileSync13(source, "utf-8"));
|
|
2750
2941
|
if (backup) {
|
|
2751
2942
|
result.backups.push(backup);
|
|
2752
2943
|
}
|
|
@@ -2770,8 +2961,8 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2770
2961
|
}
|
|
2771
2962
|
accumulateCategory(result.categories, entry);
|
|
2772
2963
|
}
|
|
2773
|
-
const hookDir =
|
|
2774
|
-
if (
|
|
2964
|
+
const hookDir = join13(projectDir, ".claude/hooks");
|
|
2965
|
+
if (existsSync14(hookDir)) {
|
|
2775
2966
|
chmodHooksSync(hookDir);
|
|
2776
2967
|
}
|
|
2777
2968
|
writeInstalledTracks(projectDir, manifestSpec.tracks);
|
|
@@ -2785,9 +2976,9 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2785
2976
|
return result;
|
|
2786
2977
|
}
|
|
2787
2978
|
function harnessAnchorLog(projectDir) {
|
|
2788
|
-
const anchor =
|
|
2789
|
-
if (!
|
|
2790
|
-
return { path: HARNESS_ANCHOR_FILE, sha256: hashContent(
|
|
2979
|
+
const anchor = join13(projectDir, HARNESS_ANCHOR_FILE);
|
|
2980
|
+
if (!existsSync14(anchor)) return null;
|
|
2981
|
+
return { path: HARNESS_ANCHOR_FILE, sha256: hashContent(readFileSync13(anchor, "utf-8")) };
|
|
2791
2982
|
}
|
|
2792
2983
|
function writeEnvironmentFiles(projectDir, tracks) {
|
|
2793
2984
|
return {
|
|
@@ -2845,8 +3036,8 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2845
3036
|
claudeDirMovedAside,
|
|
2846
3037
|
rootFiles
|
|
2847
3038
|
);
|
|
2848
|
-
const skillFiles = collectSkillHashes(ctx.projectDir);
|
|
2849
|
-
const policyFiles = collectPolicyHashes(ctx.projectDir,
|
|
3039
|
+
const skillFiles = collectSkillHashes(ctx.projectDir, join13(ctx.harnessRoot, "templates"));
|
|
3040
|
+
const policyFiles = collectPolicyHashes(ctx.projectDir, join13(ctx.harnessRoot, "templates"));
|
|
2850
3041
|
const merged = mergeExternalFiles(ctx.projectDir, previousLog?.externalFiles, externalFiles);
|
|
2851
3042
|
writeInstallLog(ctx.projectDir, {
|
|
2852
3043
|
...log,
|
|
@@ -2862,11 +3053,11 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2862
3053
|
}
|
|
2863
3054
|
}
|
|
2864
3055
|
function composeAndWriteMcp(harnessRoot, projectDir, spec) {
|
|
2865
|
-
const mcpPath =
|
|
2866
|
-
const created = !
|
|
3056
|
+
const mcpPath = join13(projectDir, ".mcp.json");
|
|
3057
|
+
const created = !existsSync14(mcpPath);
|
|
2867
3058
|
const composed = composeMcpJson({
|
|
2868
|
-
templateMcpPath:
|
|
2869
|
-
trackMapPath:
|
|
3059
|
+
templateMcpPath: join13(harnessRoot, "templates/mcp.json"),
|
|
3060
|
+
trackMapPath: join13(harnessRoot, "templates/track-mcp-map.tsv"),
|
|
2870
3061
|
existingPath: mcpPath,
|
|
2871
3062
|
tracks: spec.tracks
|
|
2872
3063
|
});
|
|
@@ -2919,15 +3110,15 @@ function accumulateCategory(cats, entry) {
|
|
|
2919
3110
|
}
|
|
2920
3111
|
}
|
|
2921
3112
|
function writeInstalledTracks(projectDir, tracks) {
|
|
2922
|
-
const path =
|
|
3113
|
+
const path = join13(projectDir, ".claude/.installed-tracks");
|
|
2923
3114
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
2924
3115
|
const sorted = [...new Set(tracks)].sort().join("\n");
|
|
2925
3116
|
writeFileSync6(path, `${sorted}
|
|
2926
3117
|
`);
|
|
2927
3118
|
}
|
|
2928
3119
|
function writeRootClaudeMd(projectDir, tracks, continuousSkills) {
|
|
2929
|
-
const target =
|
|
2930
|
-
const existing =
|
|
3120
|
+
const target = join13(projectDir, "CLAUDE.md");
|
|
3121
|
+
const existing = existsSync14(target) ? readFileSync13(target, "utf-8") : null;
|
|
2931
3122
|
const content = upsertHarnessImport(existing, {
|
|
2932
3123
|
projectName: basename5(projectDir),
|
|
2933
3124
|
tracks,
|
|
@@ -2952,8 +3143,8 @@ function listHookFiles(hookDir) {
|
|
|
2952
3143
|
|
|
2953
3144
|
// src/commands/install-render.ts
|
|
2954
3145
|
init_esm_shims();
|
|
2955
|
-
import { existsSync as
|
|
2956
|
-
import { join as
|
|
3146
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5 } from "fs";
|
|
3147
|
+
import { join as join14 } from "path";
|
|
2957
3148
|
|
|
2958
3149
|
// src/preset-recommend.ts
|
|
2959
3150
|
init_esm_shims();
|
|
@@ -3022,7 +3213,11 @@ function renderInstallHeader(log, spec, mode) {
|
|
|
3022
3213
|
if (mode !== "update") {
|
|
3023
3214
|
const assetSpec = buildManifestSpec(spec);
|
|
3024
3215
|
const cost = formatResidentCostLine(
|
|
3025
|
-
residentCost(
|
|
3216
|
+
residentCost(
|
|
3217
|
+
buildManifest(assetSpec).filter(
|
|
3218
|
+
(e2) => e2.applies(assetSpec) && landsOnDisk(e2.target, spec.cli)
|
|
3219
|
+
)
|
|
3220
|
+
),
|
|
3026
3221
|
summarizeContextCost(finalAssets).unmeasuredCount
|
|
3027
3222
|
);
|
|
3028
3223
|
if (cost) log(` ${c.dim(`\xB7 ${cost}`)}`);
|
|
@@ -3071,6 +3266,10 @@ function createInstallRenderer(log, spec, verbose) {
|
|
|
3071
3266
|
log(` ${c.bold(`\u2501\u2501 ${CATEGORY_TITLES[asset.category]} \u2501\u2501`)}`);
|
|
3072
3267
|
currentCategory = asset.category;
|
|
3073
3268
|
}
|
|
3269
|
+
if (asset.method.kind === "npm" || asset.method.kind === "npx-run") {
|
|
3270
|
+
const what = asset.method.kind === "npm" ? `npm install ${asset.method.pkg}@${asset.method.version}` : `npx ${asset.method.cmd}@${asset.method.version}`;
|
|
3271
|
+
log(` ${c.dim(`\u2026 ${asset.id} ${what} \u2014 running, may take a few minutes`)}`);
|
|
3272
|
+
}
|
|
3074
3273
|
},
|
|
3075
3274
|
onAssetResult: (result) => {
|
|
3076
3275
|
const meta = result.ok ? formatAssetMeta(result.asset, result.version) : result.message ?? "failed";
|
|
@@ -3095,18 +3294,33 @@ function renderUpdateSummary(log, spec, report) {
|
|
|
3095
3294
|
summarizeContextCost(finalSelectedAssets(spec.tracks, spec.userOverride)).unmeasuredCount
|
|
3096
3295
|
);
|
|
3097
3296
|
if (cost) log(infoRow("CONTEXT", cost));
|
|
3297
|
+
const backups = report.updateMode?.backups ?? [];
|
|
3298
|
+
if (backups.length > 0) {
|
|
3299
|
+
log(
|
|
3300
|
+
infoRow(
|
|
3301
|
+
"BACKUPS",
|
|
3302
|
+
`${backups.length} edited file(s) kept as *.backup-<time> \xB7 list: .uzys-agent-harness/update-backups.json`
|
|
3303
|
+
)
|
|
3304
|
+
);
|
|
3305
|
+
log(
|
|
3306
|
+
infoRow(
|
|
3307
|
+
"NEXT",
|
|
3308
|
+
'to re-apply your edits on the new version, ask audit-harness-fit: "update \uBC31\uC5C5\uBCF8\uC758 \uB0B4 \uD3B8\uC9D1\uC744 \uC0C8 \uD310\uC5D0 \uB2E4\uC2DC \uC5B9\uC5B4\uC918"'
|
|
3309
|
+
)
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3098
3312
|
log("");
|
|
3099
3313
|
}
|
|
3100
3314
|
function residentEntriesOnDisk(spec) {
|
|
3101
3315
|
const assetSpec = buildManifestSpec(spec);
|
|
3102
3316
|
const planned = buildManifest(assetSpec).filter((e2) => e2.applies(assetSpec));
|
|
3103
|
-
const agentsDir =
|
|
3104
|
-
const onDisk =
|
|
3317
|
+
const agentsDir = join14(spec.projectDir, ".claude", "agents");
|
|
3318
|
+
const onDisk = existsSync15(agentsDir) ? readdirSync5(agentsDir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => ({
|
|
3105
3319
|
source: `agents/${e2.name}`,
|
|
3106
3320
|
target: `.claude/agents/${e2.name}`,
|
|
3107
3321
|
// 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 descriptor 도,
|
|
3108
3322
|
// 배포판에 더는 없는 에이전트도 실제로 상주하는 것은 이쪽이다.
|
|
3109
|
-
file:
|
|
3323
|
+
file: join14(agentsDir, e2.name)
|
|
3110
3324
|
})) : [];
|
|
3111
3325
|
return [...planned.filter((e2) => !e2.target.startsWith(".claude/agents/")), ...onDisk];
|
|
3112
3326
|
}
|
|
@@ -3314,9 +3528,27 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3314
3528
|
log(assetRow("skip", `${dir} orphan prune`, `${removed.length} removed`));
|
|
3315
3529
|
}
|
|
3316
3530
|
}
|
|
3531
|
+
if (baseline.updateMode.skippedGroups.length > 0) {
|
|
3532
|
+
log(
|
|
3533
|
+
assetRow(
|
|
3534
|
+
"skip",
|
|
3535
|
+
"not selected \u2014 untouched",
|
|
3536
|
+
`${baseline.updateMode.skippedGroups.join(", ")} \xB7 run again with --only or the wizard to include`
|
|
3537
|
+
)
|
|
3538
|
+
);
|
|
3539
|
+
}
|
|
3317
3540
|
if (baseline.updateMode.claudeMdUpdated) {
|
|
3318
3541
|
log(assetRow("success", HARNESS_ANCHOR_FILE, "refreshed from template"));
|
|
3319
3542
|
}
|
|
3543
|
+
if (baseline.updateMode.anchorBackedUp) {
|
|
3544
|
+
log(
|
|
3545
|
+
assetRow(
|
|
3546
|
+
"skip",
|
|
3547
|
+
`${HARNESS_ANCHOR_FILE} edited`,
|
|
3548
|
+
"your edits backed up as *.backup-<time> \u2014 latest template is now active"
|
|
3549
|
+
)
|
|
3550
|
+
);
|
|
3551
|
+
}
|
|
3320
3552
|
if (baseline.updateMode.anchorCreated) {
|
|
3321
3553
|
log(assetRow("success", HARNESS_ANCHOR_FILE, "created from template (anchor migration)"));
|
|
3322
3554
|
}
|
|
@@ -3350,6 +3582,15 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3350
3582
|
)
|
|
3351
3583
|
);
|
|
3352
3584
|
}
|
|
3585
|
+
if (baseline.updateMode.skillsPruned.length > 0) {
|
|
3586
|
+
log(
|
|
3587
|
+
assetRow(
|
|
3588
|
+
"success",
|
|
3589
|
+
".claude/skills not in bundle",
|
|
3590
|
+
`${baseline.updateMode.skillsPruned.length} files deleted \xB7 ${baseline.updateMode.skillsPruned.join(", ")}`
|
|
3591
|
+
)
|
|
3592
|
+
);
|
|
3593
|
+
}
|
|
3353
3594
|
if (baseline.updateMode.skillsSkippedLinks.length > 0) {
|
|
3354
3595
|
log(
|
|
3355
3596
|
assetRow(
|
|
@@ -3844,8 +4085,8 @@ function registerInstallCommand(cli2) {
|
|
|
3844
4085
|
|
|
3845
4086
|
// src/commands/list.ts
|
|
3846
4087
|
init_esm_shims();
|
|
3847
|
-
import { existsSync as
|
|
3848
|
-
import { join as
|
|
4088
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
4089
|
+
import { join as join15, resolve as resolve3 } from "path";
|
|
3849
4090
|
function listAction(options = {}, deps = {}) {
|
|
3850
4091
|
const log = deps.log ?? console.log;
|
|
3851
4092
|
const err = deps.err ?? console.error;
|
|
@@ -3908,8 +4149,8 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3908
4149
|
const rows = [` ${c.dim(dirs.join(" "))}`];
|
|
3909
4150
|
const rootMd = log.templates.rootClaudeMd;
|
|
3910
4151
|
if (rootMd) {
|
|
3911
|
-
const path =
|
|
3912
|
-
const modified =
|
|
4152
|
+
const path = join15(projectDir, rootMd.path);
|
|
4153
|
+
const modified = existsSync16(path) && hashContent(readFileSync14(path, "utf8")) !== rootMd.sha256;
|
|
3913
4154
|
rows.push(
|
|
3914
4155
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
3915
4156
|
);
|
|
@@ -3917,7 +4158,7 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3917
4158
|
return rows;
|
|
3918
4159
|
}
|
|
3919
4160
|
function formatRootFileRows(rootFiles, projectDir) {
|
|
3920
|
-
const present = rootFiles.filter((f) =>
|
|
4161
|
+
const present = rootFiles.filter((f) => existsSync16(join15(projectDir, f.path)));
|
|
3921
4162
|
const width = Math.max(0, ...present.map((f) => f.path.length));
|
|
3922
4163
|
return present.map(
|
|
3923
4164
|
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(f.change === "created" ? "\uC0DD\uC131" : "\uBCD1\uD569")} ${c.dim(f.notes.join(" / "))}`
|
|
@@ -3935,15 +4176,15 @@ function registerListCommand(cli2) {
|
|
|
3935
4176
|
init_esm_shims();
|
|
3936
4177
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3937
4178
|
import {
|
|
3938
|
-
existsSync as
|
|
4179
|
+
existsSync as existsSync17,
|
|
3939
4180
|
lstatSync as lstatSync2,
|
|
3940
4181
|
readdirSync as readdirSync6,
|
|
3941
|
-
readFileSync as
|
|
3942
|
-
rmdirSync,
|
|
4182
|
+
readFileSync as readFileSync15,
|
|
4183
|
+
rmdirSync as rmdirSync2,
|
|
3943
4184
|
rmSync,
|
|
3944
4185
|
writeFileSync as writeFileSync7
|
|
3945
4186
|
} from "fs";
|
|
3946
|
-
import { dirname as dirname5, join as
|
|
4187
|
+
import { dirname as dirname5, join as join16, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
|
|
3947
4188
|
|
|
3948
4189
|
// src/uninstall-interactive.ts
|
|
3949
4190
|
init_esm_shims();
|
|
@@ -5183,7 +5424,7 @@ function settleLog(ctx, io) {
|
|
|
5183
5424
|
return true;
|
|
5184
5425
|
}
|
|
5185
5426
|
} else {
|
|
5186
|
-
io.rm(
|
|
5427
|
+
io.rm(join16(projectDir, INSTALL_LOG_DIR));
|
|
5187
5428
|
io.rm(legacyInstallLogPath(projectDir));
|
|
5188
5429
|
io.log(` ${status.success("install log removed")}`);
|
|
5189
5430
|
}
|
|
@@ -5233,7 +5474,7 @@ function advisoryLines(plan, projectDir, rootFiles) {
|
|
|
5233
5474
|
return lines;
|
|
5234
5475
|
}
|
|
5235
5476
|
function rootFileAdvisoryLines(rootFiles, projectDir) {
|
|
5236
|
-
const present = rootFiles.filter((f) =>
|
|
5477
|
+
const present = rootFiles.filter((f) => existsSync17(join16(projectDir, f.path)));
|
|
5237
5478
|
if (present.length === 0) return [];
|
|
5238
5479
|
return [
|
|
5239
5480
|
"",
|
|
@@ -5334,16 +5575,16 @@ function buildGlobalAdvisoryCmd(asset) {
|
|
|
5334
5575
|
}
|
|
5335
5576
|
}
|
|
5336
5577
|
function removeTemplates(log, projectDir, rm) {
|
|
5337
|
-
rm(
|
|
5338
|
-
if (log.templates.codexDir) rm(
|
|
5339
|
-
if (log.templates.opencodeDir) rm(
|
|
5578
|
+
rm(join16(projectDir, log.templates.claudeDir));
|
|
5579
|
+
if (log.templates.codexDir) rm(join16(projectDir, log.templates.codexDir));
|
|
5580
|
+
if (log.templates.opencodeDir) rm(join16(projectDir, log.templates.opencodeDir));
|
|
5340
5581
|
const external = removeExternalFiles(log, projectDir, rm);
|
|
5341
5582
|
const importStripped = stripRootImport(projectDir);
|
|
5342
5583
|
const rootMd = log.templates.rootClaudeMd;
|
|
5343
5584
|
if (rootMd) {
|
|
5344
5585
|
if (rootClaudeMdModified(log, projectDir))
|
|
5345
5586
|
return { rootClaudeMdKept: true, importStripped, external };
|
|
5346
|
-
rm(
|
|
5587
|
+
rm(join16(projectDir, rootMd.path));
|
|
5347
5588
|
}
|
|
5348
5589
|
return { rootClaudeMdKept: false, importStripped, external };
|
|
5349
5590
|
}
|
|
@@ -5351,17 +5592,17 @@ function removeExternalFiles(log, projectDir, rm) {
|
|
|
5351
5592
|
const removed = [];
|
|
5352
5593
|
const kept = [];
|
|
5353
5594
|
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
5354
|
-
const abs =
|
|
5355
|
-
if (!
|
|
5595
|
+
const abs = join16(projectDir, path);
|
|
5596
|
+
if (!existsSync17(abs)) continue;
|
|
5356
5597
|
if (!lstatSync2(abs).isFile()) continue;
|
|
5357
|
-
if (hashContent(
|
|
5598
|
+
if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
|
|
5358
5599
|
kept.push(path);
|
|
5359
5600
|
continue;
|
|
5360
5601
|
}
|
|
5361
5602
|
rm(abs);
|
|
5362
5603
|
removed.push(path);
|
|
5363
5604
|
}
|
|
5364
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(
|
|
5605
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(join16(projectDir, path)));
|
|
5365
5606
|
return { removed, kept };
|
|
5366
5607
|
}
|
|
5367
5608
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
@@ -5371,7 +5612,7 @@ function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
|
5371
5612
|
if (rel === "" || rel.startsWith(`..${sep2}`) || rel === "..") return;
|
|
5372
5613
|
try {
|
|
5373
5614
|
if (readdirSync6(cursor).length > 0) return;
|
|
5374
|
-
|
|
5615
|
+
rmdirSync2(cursor);
|
|
5375
5616
|
} catch {
|
|
5376
5617
|
return;
|
|
5377
5618
|
}
|
|
@@ -5383,7 +5624,7 @@ function stripRootImport(projectDir) {
|
|
|
5383
5624
|
const stripped = current === null ? null : stripHarnessImport(current);
|
|
5384
5625
|
if (stripped === null) return false;
|
|
5385
5626
|
try {
|
|
5386
|
-
writeFileSync7(
|
|
5627
|
+
writeFileSync7(join16(projectDir, "CLAUDE.md"), stripped, "utf8");
|
|
5387
5628
|
return true;
|
|
5388
5629
|
} catch {
|
|
5389
5630
|
return false;
|
|
@@ -5393,9 +5634,9 @@ function previewExternalLines(installLog, projectDir) {
|
|
|
5393
5634
|
const lines = [];
|
|
5394
5635
|
let removable = 0;
|
|
5395
5636
|
for (const { path, sha256 } of installLog.externalFiles ?? []) {
|
|
5396
|
-
const abs =
|
|
5397
|
-
if (!
|
|
5398
|
-
if (hashContent(
|
|
5637
|
+
const abs = join16(projectDir, path);
|
|
5638
|
+
if (!existsSync17(abs)) continue;
|
|
5639
|
+
if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
|
|
5399
5640
|
lines.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
|
|
5400
5641
|
continue;
|
|
5401
5642
|
}
|
|
@@ -5421,10 +5662,10 @@ function hasRootImport(projectDir) {
|
|
|
5421
5662
|
return current !== null && stripHarnessImport(current) !== null;
|
|
5422
5663
|
}
|
|
5423
5664
|
function readRootClaudeMd(projectDir) {
|
|
5424
|
-
const path =
|
|
5425
|
-
if (!
|
|
5665
|
+
const path = join16(projectDir, "CLAUDE.md");
|
|
5666
|
+
if (!existsSync17(path)) return null;
|
|
5426
5667
|
try {
|
|
5427
|
-
return
|
|
5668
|
+
return readFileSync15(path, "utf8");
|
|
5428
5669
|
} catch {
|
|
5429
5670
|
return null;
|
|
5430
5671
|
}
|
|
@@ -5432,9 +5673,9 @@ function readRootClaudeMd(projectDir) {
|
|
|
5432
5673
|
function rootClaudeMdModified(log, projectDir) {
|
|
5433
5674
|
const rootMd = log.templates.rootClaudeMd;
|
|
5434
5675
|
if (!rootMd) return false;
|
|
5435
|
-
const path =
|
|
5436
|
-
if (!
|
|
5437
|
-
return hashContent(
|
|
5676
|
+
const path = join16(projectDir, rootMd.path);
|
|
5677
|
+
if (!existsSync17(path)) return false;
|
|
5678
|
+
return hashContent(readFileSync15(path, "utf8")) !== rootMd.sha256;
|
|
5438
5679
|
}
|
|
5439
5680
|
function formatTemplateList(log) {
|
|
5440
5681
|
const items = [log.templates.claudeDir];
|
|
@@ -5446,7 +5687,7 @@ function defaultSpawn2(cmd, args) {
|
|
|
5446
5687
|
return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
|
|
5447
5688
|
}
|
|
5448
5689
|
function defaultRm(path) {
|
|
5449
|
-
if (
|
|
5690
|
+
if (existsSync17(path)) {
|
|
5450
5691
|
rmSync(path, { recursive: true, force: true });
|
|
5451
5692
|
}
|
|
5452
5693
|
}
|
|
@@ -5494,8 +5735,8 @@ import { resolve as resolve5 } from "path";
|
|
|
5494
5735
|
|
|
5495
5736
|
// src/state.ts
|
|
5496
5737
|
init_esm_shims();
|
|
5497
|
-
import { existsSync as
|
|
5498
|
-
import { join as
|
|
5738
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
5739
|
+
import { join as join17 } from "path";
|
|
5499
5740
|
var META_FILE = ".claude/.installed-tracks";
|
|
5500
5741
|
var LEGACY_SIGNATURES = [
|
|
5501
5742
|
{ rule: "htmx.md", track: "ssr-htmx" },
|
|
@@ -5505,11 +5746,11 @@ var LEGACY_SIGNATURES = [
|
|
|
5505
5746
|
{ rule: "cli-development.md", track: "tooling" }
|
|
5506
5747
|
];
|
|
5507
5748
|
function detectInstallState(projectDir) {
|
|
5508
|
-
const claudeDir =
|
|
5509
|
-
const hasClaudeDir =
|
|
5749
|
+
const claudeDir = join17(projectDir, ".claude");
|
|
5750
|
+
const hasClaudeDir = existsSync18(claudeDir);
|
|
5510
5751
|
if (hasClaudeDir) {
|
|
5511
|
-
const metaPath =
|
|
5512
|
-
if (
|
|
5752
|
+
const metaPath = join17(projectDir, META_FILE);
|
|
5753
|
+
if (existsSync18(metaPath)) {
|
|
5513
5754
|
const tracks2 = readMetafile(metaPath);
|
|
5514
5755
|
return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
|
|
5515
5756
|
}
|
|
@@ -5531,7 +5772,7 @@ function tracksFromLog(tracks) {
|
|
|
5531
5772
|
return [...new Set(tracks.filter(isTrack))].sort();
|
|
5532
5773
|
}
|
|
5533
5774
|
function readMetafile(path) {
|
|
5534
|
-
const raw =
|
|
5775
|
+
const raw = readFileSync16(path, "utf8");
|
|
5535
5776
|
const seen = /* @__PURE__ */ new Set();
|
|
5536
5777
|
for (const line of raw.split(/\s+/)) {
|
|
5537
5778
|
const trimmed = line.trim();
|
|
@@ -5542,13 +5783,13 @@ function readMetafile(path) {
|
|
|
5542
5783
|
return [...seen].sort();
|
|
5543
5784
|
}
|
|
5544
5785
|
function inferFromLegacySignatures(projectDir) {
|
|
5545
|
-
const rulesDir =
|
|
5546
|
-
if (!
|
|
5786
|
+
const rulesDir = join17(projectDir, ".claude/rules");
|
|
5787
|
+
if (!existsSync18(rulesDir)) {
|
|
5547
5788
|
return [];
|
|
5548
5789
|
}
|
|
5549
5790
|
const found = /* @__PURE__ */ new Set();
|
|
5550
5791
|
for (const sig of LEGACY_SIGNATURES) {
|
|
5551
|
-
if (
|
|
5792
|
+
if (existsSync18(join17(rulesDir, sig.rule))) {
|
|
5552
5793
|
found.add(sig.track);
|
|
5553
5794
|
}
|
|
5554
5795
|
}
|
|
@@ -5570,8 +5811,21 @@ function updateAction(options = {}, deps = {}) {
|
|
|
5570
5811
|
exit(1);
|
|
5571
5812
|
return;
|
|
5572
5813
|
}
|
|
5814
|
+
const rawOnly = options.only === void 0 ? [] : [options.only].flat().map((v2) => typeof v2 === "string" ? v2 : "(missing value)");
|
|
5815
|
+
const parsed = parseUpdateOnly(rawOnly);
|
|
5816
|
+
if (!parsed.ok) {
|
|
5817
|
+
err(status.failure(c.red(`Unknown --only value: ${parsed.invalid.join(", ")}`)));
|
|
5818
|
+
err(c.dim(` Valid groups: ${UPDATE_GROUPS.join(" | ")}`));
|
|
5819
|
+
exit(1);
|
|
5820
|
+
return;
|
|
5821
|
+
}
|
|
5573
5822
|
log(c.dim(`Updating installed harness files in ${projectDir}`));
|
|
5574
|
-
execute(buildUpdateSpec(projectDir, state.tracks), {
|
|
5823
|
+
execute(buildUpdateSpec(projectDir, state.tracks, parsed.groups), {
|
|
5824
|
+
log,
|
|
5825
|
+
err,
|
|
5826
|
+
exit,
|
|
5827
|
+
mode: "update"
|
|
5828
|
+
});
|
|
5575
5829
|
}
|
|
5576
5830
|
function registerUpdateCommand(cli2) {
|
|
5577
5831
|
cli2.command(
|
|
@@ -5579,7 +5833,10 @@ function registerUpdateCommand(cli2) {
|
|
|
5579
5833
|
"Refresh installed policy files (rules / agents / commands / hooks / skills)"
|
|
5580
5834
|
).option("--project-dir <path>", "[Project] Target project directory", {
|
|
5581
5835
|
default: process.cwd()
|
|
5582
|
-
}).
|
|
5836
|
+
}).option(
|
|
5837
|
+
"--only <group>",
|
|
5838
|
+
`[Scope] Update only this group (repeatable): ${UPDATE_GROUPS.join(" | ")}`
|
|
5839
|
+
).action((options) => {
|
|
5583
5840
|
updateAction(options);
|
|
5584
5841
|
});
|
|
5585
5842
|
}
|
|
@@ -5694,19 +5951,6 @@ var INSTALL_TARGET_PAGES = [
|
|
|
5694
5951
|
{ label: "Visual & Media (Slides \xB7 Diagrams \xB7 Motion \xB7 Video)", cats: ["visual-media"] },
|
|
5695
5952
|
{ label: "Workflow & ECC Suite", cats: ["workflow", "ecc-suite"] }
|
|
5696
5953
|
];
|
|
5697
|
-
var DEV_METHOD_BUNDLE_VALUE = "bundle:dev-method";
|
|
5698
|
-
var DEV_METHOD_BUNDLE_CATEGORY = "workflow";
|
|
5699
|
-
var bundleMemberValues = () => DEV_METHOD_SKILL_IDS.map((id) => `asset:${id}`);
|
|
5700
|
-
function collapseDevMethodBundle(ids) {
|
|
5701
|
-
const members = new Set(bundleMemberValues());
|
|
5702
|
-
const rest = ids.filter((v2) => !members.has(v2));
|
|
5703
|
-
const anyMember = ids.some((v2) => members.has(v2));
|
|
5704
|
-
return anyMember ? [...rest, DEV_METHOD_BUNDLE_VALUE] : rest;
|
|
5705
|
-
}
|
|
5706
|
-
function expandDevMethodBundle(ids) {
|
|
5707
|
-
const rest = ids.filter((v2) => v2 !== DEV_METHOD_BUNDLE_VALUE);
|
|
5708
|
-
return ids.includes(DEV_METHOD_BUNDLE_VALUE) ? [...rest, ...bundleMemberValues()] : rest;
|
|
5709
|
-
}
|
|
5710
5954
|
var BASELINE_TITLES = {
|
|
5711
5955
|
rules: "Rules \u2014 \uC0C1\uC2DC \uC0C1\uC8FC\uD558\uB294 \uB300\uC6D0\uCE59 (4 CLI \uC804\uBD80\uC5D0 \uC124\uCE58)",
|
|
5712
5956
|
hooks: "Hooks \u2014 \uACB0\uC815\uB860\uC801 \uAC00\uB4DC (\uD574\uC81C\uD558\uBA74 settings.json \uBC30\uC120\uB3C4 \uD568\uAED8 \uBE60\uC9C4\uB2E4)",
|
|
@@ -5720,7 +5964,8 @@ function buildPageGroups(cats, initialSet, installedSet = /* @__PURE__ */ new Se
|
|
|
5720
5964
|
for (const kind of baselineKinds) {
|
|
5721
5965
|
const items = baselineTargets.filter((t) => t.kind === kind).map((t) => ({
|
|
5722
5966
|
value: t.id,
|
|
5723
|
-
label: ` ${t.name}${installedMark(t.id)}
|
|
5967
|
+
label: ` ${t.name}${installedMark(t.id)}`,
|
|
5968
|
+
...t.hint ? { hint: t.hint } : {}
|
|
5724
5969
|
}));
|
|
5725
5970
|
if (items.length === 0) continue;
|
|
5726
5971
|
groups[BASELINE_TITLES[kind]] = items;
|
|
@@ -5736,16 +5981,10 @@ function buildPageGroups(cats, initialSet, installedSet = /* @__PURE__ */ new Se
|
|
|
5736
5981
|
hint: o.hint
|
|
5737
5982
|
});
|
|
5738
5983
|
}
|
|
5739
|
-
if (cat === DEV_METHOD_BUNDLE_CATEGORY) {
|
|
5740
|
-
items.push({
|
|
5741
|
-
value: DEV_METHOD_BUNDLE_VALUE,
|
|
5742
|
-
label: ` uzys \uD558\uB124\uC2A4 \uBC29\uBC95\uB860 ${DEV_METHOD_SKILL_IDS.length}\uC885 [uzys] \u2605 official${installedMark(DEV_METHOD_BUNDLE_VALUE)}`,
|
|
5743
|
-
hint: DEV_METHOD_SKILL_IDS.join(", ")
|
|
5744
|
-
});
|
|
5745
|
-
}
|
|
5746
5984
|
const tierOrder = { official: 0, vetted: 1, experimental: 2 };
|
|
5747
|
-
const
|
|
5748
|
-
|
|
5985
|
+
const catAssets = [...EXTERNAL_ASSETS.filter((x) => x.category === cat)].sort(
|
|
5986
|
+
(a, b2) => tierOrder[assetTrustTier(a.id)] - tierOrder[assetTrustTier(b2.id)]
|
|
5987
|
+
);
|
|
5749
5988
|
for (const a of catAssets) {
|
|
5750
5989
|
const tier = assetTrustTier(a.id);
|
|
5751
5990
|
const badge = tier === "official" ? " \u2605 official" : tier === "experimental" ? " \u26A0 experimental (opt-in)" : "";
|
|
@@ -5851,6 +6090,42 @@ var defaultPrompts = {
|
|
|
5851
6090
|
});
|
|
5852
6091
|
return q(result) ? null : result;
|
|
5853
6092
|
},
|
|
6093
|
+
selectUpdateGroups: async () => {
|
|
6094
|
+
const result = await ve({
|
|
6095
|
+
message: "What to update (space = toggle \xB7 your edits are always backed up)",
|
|
6096
|
+
options: [
|
|
6097
|
+
{ value: "skills", label: "Skills", hint: "installed bundled skills \u2192 latest" },
|
|
6098
|
+
{
|
|
6099
|
+
value: "new-skills",
|
|
6100
|
+
label: "New skills",
|
|
6101
|
+
hint: "bundled skills added since your install"
|
|
6102
|
+
},
|
|
6103
|
+
{
|
|
6104
|
+
value: "rules",
|
|
6105
|
+
label: "Rules \xB7 agents \xB7 commands",
|
|
6106
|
+
hint: "your edited files \u2192 *.backup-<time>"
|
|
6107
|
+
},
|
|
6108
|
+
{
|
|
6109
|
+
value: "anchor",
|
|
6110
|
+
label: "CLAUDE-uzys-harness.md anchor",
|
|
6111
|
+
hint: "your edited anchor \u2192 *.backup-<time>"
|
|
6112
|
+
},
|
|
6113
|
+
{
|
|
6114
|
+
value: "hooks",
|
|
6115
|
+
label: "Hooks \xB7 settings.json",
|
|
6116
|
+
hint: "stale hook refs cleaned"
|
|
6117
|
+
},
|
|
6118
|
+
{
|
|
6119
|
+
value: "external",
|
|
6120
|
+
label: "Codex / OpenCode / Antigravity + external skills",
|
|
6121
|
+
hint: "rendered artifacts + npx skills"
|
|
6122
|
+
}
|
|
6123
|
+
],
|
|
6124
|
+
initialValues: [...UPDATE_GROUPS],
|
|
6125
|
+
required: true
|
|
6126
|
+
});
|
|
6127
|
+
return q(result) ? null : result;
|
|
6128
|
+
},
|
|
5854
6129
|
confirmInstall: async (summary) => {
|
|
5855
6130
|
const result = await ue({
|
|
5856
6131
|
message: `${summary}
|
|
@@ -5862,14 +6137,11 @@ Proceed?`,
|
|
|
5862
6137
|
},
|
|
5863
6138
|
selectInstallTargets: async (initialChecked, step, recap) => {
|
|
5864
6139
|
const pages = INSTALL_TARGET_PAGES;
|
|
5865
|
-
const baselineTargets = listBaselineTargets({ tracks: recap?.tracks ?? [] });
|
|
5866
|
-
const
|
|
5867
|
-
const
|
|
5868
|
-
const collected = new Set(displayInitial);
|
|
6140
|
+
const baselineTargets = withBaselineHints(listBaselineTargets({ tracks: recap?.tracks ?? [] }));
|
|
6141
|
+
const initialSet = new Set(initialChecked);
|
|
6142
|
+
const collected = new Set(initialChecked);
|
|
5869
6143
|
const recapLine = recap ? `Tracks: ${recap.tracks.join(", ")} \xB7 CLIs: ${recap.cli.join(", ")}` : "";
|
|
5870
|
-
const installedSet = new Set(
|
|
5871
|
-
collapseDevMethodBundle(recap?.installed ?? [])
|
|
5872
|
-
);
|
|
6144
|
+
const installedSet = new Set(recap?.installed ?? []);
|
|
5873
6145
|
process.stdout.write("\x1B[?1049h");
|
|
5874
6146
|
let resultIds = null;
|
|
5875
6147
|
let aborted = false;
|
|
@@ -5929,7 +6201,7 @@ Proceed?`,
|
|
|
5929
6201
|
pageIdx++;
|
|
5930
6202
|
}
|
|
5931
6203
|
if (!aborted) {
|
|
5932
|
-
resultIds =
|
|
6204
|
+
resultIds = [...collected];
|
|
5933
6205
|
}
|
|
5934
6206
|
} finally {
|
|
5935
6207
|
process.stdout.write("\x1B[?1049l");
|
|
@@ -6019,11 +6291,15 @@ async function runInteractive(projectDir, deps = {}) {
|
|
|
6019
6291
|
}
|
|
6020
6292
|
if (action === "update") {
|
|
6021
6293
|
mode = "update";
|
|
6022
|
-
const
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6294
|
+
const groups = prompts.selectUpdateGroups ? await prompts.selectUpdateGroups() : null;
|
|
6295
|
+
if (groups === null && prompts.selectUpdateGroups) {
|
|
6296
|
+
prompts.cancel("Cancelled.");
|
|
6297
|
+
return { ok: false, reason: "cancelled" };
|
|
6298
|
+
}
|
|
6299
|
+
const spec = buildUpdateSpec(projectDir, state.tracks, groups ?? void 0);
|
|
6300
|
+
const scopeLine = spec.updateOnly ? `UPDATE ${spec.updateOnly.join(" \xB7 ")} only (untouched: ${UPDATE_GROUPS.filter((g) => !spec.updateOnly?.includes(g)).join(", ")}):` : "UPDATE installed harness files:";
|
|
6301
|
+
const confirmed = await prompts.confirmInstall(`${scopeLine}
|
|
6302
|
+
${formatSummary(spec)}`);
|
|
6027
6303
|
if (!confirmed) {
|
|
6028
6304
|
prompts.outro("Cancelled.");
|
|
6029
6305
|
return { ok: false, reason: "cancelled" };
|
|
@@ -6170,7 +6446,7 @@ function formatSummary(spec) {
|
|
|
6170
6446
|
const cost = formatResidentCostLine(
|
|
6171
6447
|
residentCost(
|
|
6172
6448
|
buildManifest(assetSpec).filter(
|
|
6173
|
-
(e2) => e2.applies(assetSpec) && !isBaselineExcluded(e2.target, baselineExcluded)
|
|
6449
|
+
(e2) => e2.applies(assetSpec) && !isBaselineExcluded(e2.target, baselineExcluded) && landsOnDisk(e2.target, spec.cli)
|
|
6174
6450
|
)
|
|
6175
6451
|
),
|
|
6176
6452
|
summarizeContextCost(finalAssets).unmeasuredCount
|