@uzysjung/agent-harness 26.152.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-EQDC2AAU.js → chunk-242TVGMZ.js} +582 -556
- package/dist/chunk-242TVGMZ.js.map +1 -0
- package/dist/index.js +623 -303
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/templates/CLAUDE.md +93 -145
- package/templates/codex/README.md +6 -7
- package/templates/codex/config.toml.template +1 -9
- package/templates/skills/audit-harness-fit/README.md +76 -43
- package/templates/skills/audit-harness-fit/SKILL.md +62 -47
- package/templates/skills/audit-harness-fit/evals/scenarios.yaml +179 -22
- package/templates/skills/audit-harness-fit/references/apply.md +74 -45
- package/templates/skills/audit-harness-fit/references/audit.md +142 -118
- package/templates/skills/audit-harness-fit/references/populate.md +53 -49
- package/templates/skills/audit-harness-fit/references/verification.md +131 -100
- package/templates/skills/clear-korean-communication/SKILL.md +76 -209
- package/templates/skills/external-model-consult/scripts/codex-ask.sh +9 -4
- package/templates/skills/external-model-consult/scripts/gemini-ask.sh +9 -4
- package/templates/skills/model-orchestration/SKILL.md +123 -267
- package/templates/skills/objective-brief/SKILL.md +3 -3
- package/dist/chunk-EQDC2AAU.js.map +0 -1
- package/templates/codex/hooks/README.md +0 -37
- package/templates/codex/hooks/session-start.sh +0 -7
- package/templates/codex/hooks/uncommitted-check.sh +0 -7
- package/templates/skills/clear-korean-communication/references/pre-send-checklist.md +0 -37
- package/templates/skills/clear-korean-communication/references/why-it-works.md +0 -24
- package/templates/skills/clear-korean-communication/references/worked-examples.md +0 -89
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,
|
|
@@ -18,6 +17,8 @@ import {
|
|
|
18
17
|
RETIRED_AGENT_IDS,
|
|
19
18
|
RETIRED_SKILL_IDS,
|
|
20
19
|
TRACKS,
|
|
20
|
+
TRACK_AGENTS,
|
|
21
|
+
UPDATE_GROUPS,
|
|
21
22
|
__commonJS,
|
|
22
23
|
__toESM,
|
|
23
24
|
anyTrack,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
ensureDir,
|
|
39
40
|
ensureProjectSkeleton,
|
|
40
41
|
experimentalOptInCandidates,
|
|
42
|
+
extractFrontmatter,
|
|
41
43
|
filterApplicableAssets,
|
|
42
44
|
formatResidentCostLine,
|
|
43
45
|
hasUiTrack,
|
|
@@ -50,12 +52,14 @@ import {
|
|
|
50
52
|
isHarnessOwned,
|
|
51
53
|
isInstallScope,
|
|
52
54
|
isTrack,
|
|
55
|
+
landsOnDisk,
|
|
53
56
|
legacyInstallLogPath,
|
|
54
57
|
listFilesRecursive,
|
|
55
58
|
mergeExternalFiles,
|
|
56
59
|
readInstallLog,
|
|
57
60
|
renderFillScaffold,
|
|
58
61
|
residentCost,
|
|
62
|
+
resolveBundleRoot,
|
|
59
63
|
resolveRules,
|
|
60
64
|
resolveScope,
|
|
61
65
|
stripHarnessImport,
|
|
@@ -63,7 +67,7 @@ import {
|
|
|
63
67
|
upsertHarnessImport,
|
|
64
68
|
withContinuousSkillsNote,
|
|
65
69
|
writeInstallLog
|
|
66
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-242TVGMZ.js";
|
|
67
71
|
|
|
68
72
|
// node_modules/sisteransi/src/index.js
|
|
69
73
|
var require_src = __commonJS({
|
|
@@ -748,7 +752,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
748
752
|
// package.json
|
|
749
753
|
var package_default = {
|
|
750
754
|
name: "@uzysjung/agent-harness",
|
|
751
|
-
version: "26.
|
|
755
|
+
version: "26.154.0",
|
|
752
756
|
description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
|
|
753
757
|
type: "module",
|
|
754
758
|
publishConfig: {
|
|
@@ -813,6 +817,8 @@ import { fileURLToPath } from "url";
|
|
|
813
817
|
|
|
814
818
|
// src/baseline-targets.ts
|
|
815
819
|
init_esm_shims();
|
|
820
|
+
import { existsSync, readFileSync } from "fs";
|
|
821
|
+
import { join } from "path";
|
|
816
822
|
var BASELINE_KINDS = ["rules", "agents", "hooks", "skills"];
|
|
817
823
|
var BASELINE_PREFIX = "baseline:";
|
|
818
824
|
var PREFIX_BY_KIND = [
|
|
@@ -850,6 +856,42 @@ function isBaselineExcluded(target, excluded) {
|
|
|
850
856
|
const t = classifyBaselineTarget(target);
|
|
851
857
|
return t !== null && excluded.has(t.id);
|
|
852
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
|
+
}
|
|
853
895
|
|
|
854
896
|
// src/cli-targets.ts
|
|
855
897
|
init_esm_shims();
|
|
@@ -987,18 +1029,18 @@ function padDisplay(s, width) {
|
|
|
987
1029
|
init_esm_shims();
|
|
988
1030
|
import {
|
|
989
1031
|
chmodSync as chmodSync3,
|
|
990
|
-
existsSync as
|
|
1032
|
+
existsSync as existsSync14,
|
|
991
1033
|
mkdirSync as mkdirSync4,
|
|
992
1034
|
readdirSync as readdirSync4,
|
|
993
|
-
readFileSync as
|
|
1035
|
+
readFileSync as readFileSync13,
|
|
994
1036
|
writeFileSync as writeFileSync6
|
|
995
1037
|
} from "fs";
|
|
996
|
-
import { basename as basename5, dirname as dirname4, join as
|
|
1038
|
+
import { basename as basename5, dirname as dirname4, join as join13, resolve } from "path";
|
|
997
1039
|
|
|
998
1040
|
// src/ci-scaffold.ts
|
|
999
1041
|
init_esm_shims();
|
|
1000
|
-
import { existsSync } from "fs";
|
|
1001
|
-
import { join } from "path";
|
|
1042
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1043
|
+
import { join as join2 } from "path";
|
|
1002
1044
|
var PYTHON_TRACKS = "data|csr-fastapi|full";
|
|
1003
1045
|
var VARIANTS = [
|
|
1004
1046
|
{
|
|
@@ -1022,12 +1064,12 @@ function installCiScaffold(ctx) {
|
|
|
1022
1064
|
for (const v2 of VARIANTS) {
|
|
1023
1065
|
if (!v2.applies(ctx.tracks)) continue;
|
|
1024
1066
|
const relTarget = `.github/workflows/${v2.target}`;
|
|
1025
|
-
const target =
|
|
1026
|
-
if (
|
|
1067
|
+
const target = join2(ctx.projectDir, relTarget);
|
|
1068
|
+
if (existsSync2(target)) {
|
|
1027
1069
|
report.skippedExisting.push(relTarget);
|
|
1028
1070
|
continue;
|
|
1029
1071
|
}
|
|
1030
|
-
copyFile(
|
|
1072
|
+
copyFile(join2(ctx.harnessRoot, "templates/github-workflows", v2.source), target);
|
|
1031
1073
|
report.written.push(relTarget);
|
|
1032
1074
|
}
|
|
1033
1075
|
return report;
|
|
@@ -1038,8 +1080,8 @@ init_esm_shims();
|
|
|
1038
1080
|
|
|
1039
1081
|
// src/antigravity/transform.ts
|
|
1040
1082
|
init_esm_shims();
|
|
1041
|
-
import { existsSync as
|
|
1042
|
-
import { basename, join as
|
|
1083
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1084
|
+
import { basename, join as join6 } from "path";
|
|
1043
1085
|
|
|
1044
1086
|
// src/codex/agents-md.ts
|
|
1045
1087
|
init_esm_shims();
|
|
@@ -1054,8 +1096,8 @@ function renderAgentsMd(params) {
|
|
|
1054
1096
|
|
|
1055
1097
|
// src/codex/skills.ts
|
|
1056
1098
|
init_esm_shims();
|
|
1057
|
-
import { chmodSync, existsSync as
|
|
1058
|
-
import { join as
|
|
1099
|
+
import { chmodSync, existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1100
|
+
import { join as join3 } from "path";
|
|
1059
1101
|
function renderBundledSkill(source) {
|
|
1060
1102
|
const trimmed = source.trimEnd();
|
|
1061
1103
|
const lines = trimmed.split(/\r?\n/);
|
|
@@ -1087,14 +1129,14 @@ function writeBundledSkillDirs(params) {
|
|
|
1087
1129
|
const { harnessRoot, projectDir, skillIds, writer } = params;
|
|
1088
1130
|
const written = [];
|
|
1089
1131
|
for (const id of skillIds) {
|
|
1090
|
-
const srcDir =
|
|
1091
|
-
const srcSkill =
|
|
1092
|
-
if (!
|
|
1132
|
+
const srcDir = join3(harnessRoot, "templates/skills", id);
|
|
1133
|
+
const srcSkill = join3(srcDir, "SKILL.md");
|
|
1134
|
+
if (!existsSync3(srcSkill)) {
|
|
1093
1135
|
continue;
|
|
1094
1136
|
}
|
|
1095
|
-
const targetDir =
|
|
1096
|
-
const targetSkill =
|
|
1097
|
-
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")))) {
|
|
1098
1140
|
continue;
|
|
1099
1141
|
}
|
|
1100
1142
|
written.push(targetSkill);
|
|
@@ -1102,10 +1144,10 @@ function writeBundledSkillDirs(params) {
|
|
|
1102
1144
|
if (rel === "SKILL.md") continue;
|
|
1103
1145
|
if (rel.split("/").some((segment) => segment.startsWith("."))) continue;
|
|
1104
1146
|
const segments = rel.split("/");
|
|
1105
|
-
const raw =
|
|
1147
|
+
const raw = readFileSync2(join3(srcDir, ...segments), "utf8");
|
|
1106
1148
|
const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
|
|
1107
1149
|
` : raw;
|
|
1108
|
-
const target =
|
|
1150
|
+
const target = join3(targetDir, ...segments);
|
|
1109
1151
|
if (!writer.write(target, ported, { createInRefresh: true })) continue;
|
|
1110
1152
|
if (rel.endsWith(".sh")) chmodSync(target, 493);
|
|
1111
1153
|
written.push(target);
|
|
@@ -1116,13 +1158,13 @@ function writeBundledSkillDirs(params) {
|
|
|
1116
1158
|
|
|
1117
1159
|
// src/owned-write.ts
|
|
1118
1160
|
init_esm_shims();
|
|
1119
|
-
import { existsSync as
|
|
1161
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1120
1162
|
import { dirname, relative, sep } from "path";
|
|
1121
1163
|
|
|
1122
1164
|
// src/foreign-slot.ts
|
|
1123
1165
|
init_esm_shims();
|
|
1124
1166
|
import { lstatSync } from "fs";
|
|
1125
|
-
import { join as
|
|
1167
|
+
import { join as join4 } from "path";
|
|
1126
1168
|
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
1127
1169
|
function occupiedByNonDirectory(path) {
|
|
1128
1170
|
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
@@ -1131,15 +1173,15 @@ function occupiedByNonDirectory(path) {
|
|
|
1131
1173
|
function foreignOwnedTarget(projectDir, rel) {
|
|
1132
1174
|
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
1133
1175
|
if (slot === void 0) return null;
|
|
1134
|
-
if (occupiedByNonDirectory(
|
|
1176
|
+
if (occupiedByNonDirectory(join4(projectDir, slot))) return slot;
|
|
1135
1177
|
if (rel !== slot) {
|
|
1136
1178
|
const parts = rel.slice(slot.length + 1).split("/");
|
|
1137
1179
|
let cursor = slot;
|
|
1138
1180
|
for (const part of parts.slice(0, -1)) {
|
|
1139
1181
|
cursor = `${cursor}/${part}`;
|
|
1140
|
-
if (occupiedByNonDirectory(
|
|
1182
|
+
if (occupiedByNonDirectory(join4(projectDir, cursor))) return cursor;
|
|
1141
1183
|
}
|
|
1142
|
-
const leaf = lstatSync(
|
|
1184
|
+
const leaf = lstatSync(join4(projectDir, rel), { throwIfNoEntry: false });
|
|
1143
1185
|
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
1144
1186
|
}
|
|
1145
1187
|
return null;
|
|
@@ -1162,13 +1204,13 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1162
1204
|
return false;
|
|
1163
1205
|
}
|
|
1164
1206
|
const digest = hashContent(content);
|
|
1165
|
-
if (!
|
|
1207
|
+
if (!existsSync4(absPath)) {
|
|
1166
1208
|
if (refreshOnly && !opts?.createInRefresh) return false;
|
|
1167
1209
|
mkdirSync(dirname(absPath), { recursive: true });
|
|
1168
1210
|
writeFileSync(absPath, content);
|
|
1169
1211
|
updated++;
|
|
1170
1212
|
} else {
|
|
1171
|
-
const current =
|
|
1213
|
+
const current = readFileSync3(absPath, "utf8");
|
|
1172
1214
|
if (current !== content) {
|
|
1173
1215
|
if (!isHarnessOwned(baseline, rel, current)) {
|
|
1174
1216
|
backupPaths.push(backupFile(absPath, now));
|
|
@@ -1195,8 +1237,8 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1195
1237
|
|
|
1196
1238
|
// src/rules-port.ts
|
|
1197
1239
|
init_esm_shims();
|
|
1198
|
-
import { existsSync as
|
|
1199
|
-
import { join as
|
|
1240
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1241
|
+
import { join as join5 } from "path";
|
|
1200
1242
|
function stripClaudeFrontmatter(source) {
|
|
1201
1243
|
if (!source.startsWith("---\n")) return source.trim();
|
|
1202
1244
|
const end = source.indexOf("\n---", 4);
|
|
@@ -1206,9 +1248,9 @@ function stripClaudeFrontmatter(source) {
|
|
|
1206
1248
|
function portRules(harnessRoot, ruleNames) {
|
|
1207
1249
|
const ported = [];
|
|
1208
1250
|
for (const name of ruleNames) {
|
|
1209
|
-
const src =
|
|
1210
|
-
if (!
|
|
1211
|
-
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")) });
|
|
1212
1254
|
}
|
|
1213
1255
|
return ported;
|
|
1214
1256
|
}
|
|
@@ -1232,7 +1274,7 @@ function runAntigravityTransform(params) {
|
|
|
1232
1274
|
const antigravityInstalled = rulesFile !== null;
|
|
1233
1275
|
const harnessRuleFiles = [];
|
|
1234
1276
|
for (const rule of portRules(harnessRoot, rules)) {
|
|
1235
|
-
const target =
|
|
1277
|
+
const target = join6(projectDir, ".agents", "rules", `${rule.name}.md`);
|
|
1236
1278
|
if (!writer.write(target, `${rule.body}
|
|
1237
1279
|
`, { createInRefresh: antigravityInstalled })) {
|
|
1238
1280
|
continue;
|
|
@@ -1253,14 +1295,14 @@ function runAntigravityTransform(params) {
|
|
|
1253
1295
|
};
|
|
1254
1296
|
}
|
|
1255
1297
|
function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills) {
|
|
1256
|
-
const claudeMdPath =
|
|
1257
|
-
const templatePath =
|
|
1258
|
-
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)) {
|
|
1259
1301
|
return null;
|
|
1260
1302
|
}
|
|
1261
|
-
const claudeMd =
|
|
1262
|
-
const template =
|
|
1263
|
-
const target =
|
|
1303
|
+
const claudeMd = readFileSync5(claudeMdPath, "utf8");
|
|
1304
|
+
const template = readFileSync5(templatePath, "utf8");
|
|
1305
|
+
const target = join6(projectDir, ".agents", "rules", "uzys-harness.md");
|
|
1264
1306
|
const rulesOut = renderAgentsMd({
|
|
1265
1307
|
template,
|
|
1266
1308
|
claudeMd,
|
|
@@ -1277,18 +1319,18 @@ function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills) {
|
|
|
1277
1319
|
// src/codex/opt-in.ts
|
|
1278
1320
|
init_esm_shims();
|
|
1279
1321
|
import { homedir } from "os";
|
|
1280
|
-
import { join as
|
|
1322
|
+
import { join as join7 } from "path";
|
|
1281
1323
|
|
|
1282
1324
|
// src/codex/trust-entry.ts
|
|
1283
1325
|
init_esm_shims();
|
|
1284
|
-
import { existsSync as
|
|
1326
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
1285
1327
|
import { dirname as dirname2 } from "path";
|
|
1286
1328
|
var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
|
|
1287
1329
|
function registerTrustEntry(opts) {
|
|
1288
1330
|
const { configPath, projectDir } = opts;
|
|
1289
1331
|
try {
|
|
1290
1332
|
mkdirSync2(dirname2(configPath), { recursive: true });
|
|
1291
|
-
const existing =
|
|
1333
|
+
const existing = existsSync7(configPath) ? readFileSync6(configPath, "utf8") : "";
|
|
1292
1334
|
if (hasTrustEntry(existing, projectDir)) {
|
|
1293
1335
|
return { status: "already-present" };
|
|
1294
1336
|
}
|
|
@@ -1312,8 +1354,8 @@ function hasTrustEntry(configContent, projectDir) {
|
|
|
1312
1354
|
|
|
1313
1355
|
// src/codex/opt-in.ts
|
|
1314
1356
|
function runCodexOptIn(ctx) {
|
|
1315
|
-
const codexHome = ctx.codexHome ??
|
|
1316
|
-
const configPath =
|
|
1357
|
+
const codexHome = ctx.codexHome ?? join7(homedir(), ".codex");
|
|
1358
|
+
const configPath = join7(codexHome, "config.toml");
|
|
1317
1359
|
const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
|
|
1318
1360
|
return {
|
|
1319
1361
|
trustEntry: {
|
|
@@ -1326,8 +1368,8 @@ function runCodexOptIn(ctx) {
|
|
|
1326
1368
|
|
|
1327
1369
|
// src/codex/transform.ts
|
|
1328
1370
|
init_esm_shims();
|
|
1329
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
1330
|
-
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";
|
|
1331
1373
|
|
|
1332
1374
|
// src/codex/config-toml.ts
|
|
1333
1375
|
init_esm_shims();
|
|
@@ -1404,12 +1446,12 @@ function runCodexTransform(params) {
|
|
|
1404
1446
|
refreshOnly
|
|
1405
1447
|
} = params;
|
|
1406
1448
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1407
|
-
const claudeMd = readRequired(
|
|
1408
|
-
const agentsTemplate = readRequired(
|
|
1409
|
-
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"));
|
|
1410
1452
|
const projectName = basename2(projectDir);
|
|
1411
|
-
const mcp = readOptionalJson(
|
|
1412
|
-
const agentsMdPath =
|
|
1453
|
+
const mcp = readOptionalJson(join8(harnessRoot, ".mcp.json"));
|
|
1454
|
+
const agentsMdPath = join8(projectDir, "AGENTS.md");
|
|
1413
1455
|
ensureDir(projectDir);
|
|
1414
1456
|
const agentsMdOut = renderAgentsMd({
|
|
1415
1457
|
template: agentsTemplate,
|
|
@@ -1424,7 +1466,7 @@ function runCodexTransform(params) {
|
|
|
1424
1466
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1425
1467
|
});
|
|
1426
1468
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1427
|
-
const configTomlPath =
|
|
1469
|
+
const configTomlPath = join8(projectDir, ".codex/config.toml");
|
|
1428
1470
|
writer.write(
|
|
1429
1471
|
configTomlPath,
|
|
1430
1472
|
renderConfigToml({
|
|
@@ -1434,15 +1476,15 @@ function runCodexTransform(params) {
|
|
|
1434
1476
|
mcp
|
|
1435
1477
|
})
|
|
1436
1478
|
);
|
|
1437
|
-
const hookDir =
|
|
1479
|
+
const hookDir = join8(projectDir, ".codex/hooks");
|
|
1438
1480
|
const hookFiles = [];
|
|
1439
1481
|
for (const hook of HOOK_NAMES) {
|
|
1440
|
-
const src =
|
|
1441
|
-
if (!
|
|
1482
|
+
const src = join8(harnessRoot, "templates/hooks", `${hook}.sh`);
|
|
1483
|
+
if (!existsSync8(src)) {
|
|
1442
1484
|
continue;
|
|
1443
1485
|
}
|
|
1444
|
-
const ported =
|
|
1445
|
-
const target =
|
|
1486
|
+
const ported = readFileSync7(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
|
|
1487
|
+
const target = join8(hookDir, `${hook}.sh`);
|
|
1446
1488
|
if (!writer.write(target, ported)) continue;
|
|
1447
1489
|
chmodSync2(target, 493);
|
|
1448
1490
|
hookFiles.push(target);
|
|
@@ -1462,17 +1504,17 @@ function runCodexTransform(params) {
|
|
|
1462
1504
|
};
|
|
1463
1505
|
}
|
|
1464
1506
|
function readRequired(path) {
|
|
1465
|
-
if (!
|
|
1507
|
+
if (!existsSync8(path)) {
|
|
1466
1508
|
throw new Error(`Codex transform: required source missing: ${path}`);
|
|
1467
1509
|
}
|
|
1468
|
-
return
|
|
1510
|
+
return readFileSync7(path, "utf8");
|
|
1469
1511
|
}
|
|
1470
1512
|
function readOptionalJson(path) {
|
|
1471
|
-
if (!
|
|
1513
|
+
if (!existsSync8(path)) {
|
|
1472
1514
|
return null;
|
|
1473
1515
|
}
|
|
1474
1516
|
try {
|
|
1475
|
-
return JSON.parse(
|
|
1517
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1476
1518
|
} catch {
|
|
1477
1519
|
return null;
|
|
1478
1520
|
}
|
|
@@ -1480,8 +1522,8 @@ function readOptionalJson(path) {
|
|
|
1480
1522
|
|
|
1481
1523
|
// src/opencode/transform.ts
|
|
1482
1524
|
init_esm_shims();
|
|
1483
|
-
import { existsSync as
|
|
1484
|
-
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";
|
|
1485
1527
|
|
|
1486
1528
|
// src/opencode/agents-md.ts
|
|
1487
1529
|
init_esm_shims();
|
|
@@ -1524,15 +1566,15 @@ function runOpencodeTransform(params) {
|
|
|
1524
1566
|
refreshOnly
|
|
1525
1567
|
} = params;
|
|
1526
1568
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1527
|
-
const claudeMd = readRequired2(
|
|
1528
|
-
const agentsTemplate = readRequired2(
|
|
1569
|
+
const claudeMd = readRequired2(join9(harnessRoot, "templates/CLAUDE.md"));
|
|
1570
|
+
const agentsTemplate = readRequired2(join9(harnessRoot, "templates/opencode/AGENTS.md.template"));
|
|
1529
1571
|
const opencodeTemplate = readRequired2(
|
|
1530
|
-
|
|
1572
|
+
join9(harnessRoot, "templates/opencode/opencode.json.template")
|
|
1531
1573
|
);
|
|
1532
1574
|
const projectName = basename3(projectDir);
|
|
1533
|
-
const mcp = readOptionalJson2(
|
|
1575
|
+
const mcp = readOptionalJson2(join9(harnessRoot, ".mcp.json"));
|
|
1534
1576
|
ensureDir(projectDir);
|
|
1535
|
-
const agentsMdPath =
|
|
1577
|
+
const agentsMdPath = join9(projectDir, "AGENTS.md");
|
|
1536
1578
|
const agentsMdOut = renderAgentsMd2({
|
|
1537
1579
|
template: agentsTemplate,
|
|
1538
1580
|
claudeMd,
|
|
@@ -1548,7 +1590,7 @@ function runOpencodeTransform(params) {
|
|
|
1548
1590
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1549
1591
|
});
|
|
1550
1592
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1551
|
-
const opencodeJsonPath =
|
|
1593
|
+
const opencodeJsonPath = join9(projectDir, "opencode.json");
|
|
1552
1594
|
writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
|
|
1553
1595
|
const skillFiles = writeBundledSkillDirs({
|
|
1554
1596
|
harnessRoot,
|
|
@@ -1557,13 +1599,13 @@ function runOpencodeTransform(params) {
|
|
|
1557
1599
|
writer
|
|
1558
1600
|
});
|
|
1559
1601
|
const retiredCommands = [];
|
|
1560
|
-
const cmdDir =
|
|
1561
|
-
if (
|
|
1602
|
+
const cmdDir = join9(projectDir, ".opencode/commands");
|
|
1603
|
+
if (existsSync9(cmdDir)) {
|
|
1562
1604
|
for (const entry of readdirSync(cmdDir)) {
|
|
1563
1605
|
if (!entry.endsWith(".md")) continue;
|
|
1564
1606
|
const id = entry.slice(0, -3);
|
|
1565
|
-
if (!
|
|
1566
|
-
const victim =
|
|
1607
|
+
if (!existsSync9(join9(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
|
|
1608
|
+
const victim = join9(cmdDir, entry);
|
|
1567
1609
|
try {
|
|
1568
1610
|
backupFile(victim);
|
|
1569
1611
|
unlinkSync(victim);
|
|
@@ -1581,17 +1623,17 @@ function runOpencodeTransform(params) {
|
|
|
1581
1623
|
};
|
|
1582
1624
|
}
|
|
1583
1625
|
function readRequired2(path) {
|
|
1584
|
-
if (!
|
|
1626
|
+
if (!existsSync9(path)) {
|
|
1585
1627
|
throw new Error(`OpenCode transform: required source missing: ${path}`);
|
|
1586
1628
|
}
|
|
1587
|
-
return
|
|
1629
|
+
return readFileSync8(path, "utf8");
|
|
1588
1630
|
}
|
|
1589
1631
|
function readOptionalJson2(path) {
|
|
1590
|
-
if (!
|
|
1632
|
+
if (!existsSync9(path)) {
|
|
1591
1633
|
return null;
|
|
1592
1634
|
}
|
|
1593
1635
|
try {
|
|
1594
|
-
return JSON.parse(
|
|
1636
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
1595
1637
|
} catch {
|
|
1596
1638
|
return null;
|
|
1597
1639
|
}
|
|
@@ -1683,8 +1725,8 @@ function runCliTransforms(params) {
|
|
|
1683
1725
|
|
|
1684
1726
|
// src/env-files.ts
|
|
1685
1727
|
init_esm_shims();
|
|
1686
|
-
import { appendFileSync, existsSync as
|
|
1687
|
-
import { join as
|
|
1728
|
+
import { appendFileSync, existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
|
|
1729
|
+
import { join as join10 } from "path";
|
|
1688
1730
|
var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
|
|
1689
1731
|
# Copy to .env (gitignored) and fill in values: cp .env.example .env
|
|
1690
1732
|
|
|
@@ -1721,19 +1763,19 @@ function writeEnvExample(projectDir, tracks) {
|
|
|
1721
1763
|
if (!tracks.some((t) => ENV_EXAMPLE_TRACKS.includes(t))) {
|
|
1722
1764
|
return false;
|
|
1723
1765
|
}
|
|
1724
|
-
const path =
|
|
1725
|
-
if (
|
|
1766
|
+
const path = join10(projectDir, ".env.example");
|
|
1767
|
+
if (existsSync10(path)) {
|
|
1726
1768
|
return false;
|
|
1727
1769
|
}
|
|
1728
1770
|
writeFileSync3(path, ENV_EXAMPLE_BODY);
|
|
1729
1771
|
return true;
|
|
1730
1772
|
}
|
|
1731
1773
|
function addGitignoreEnv(projectDir) {
|
|
1732
|
-
const path =
|
|
1733
|
-
if (!
|
|
1774
|
+
const path = join10(projectDir, ".gitignore");
|
|
1775
|
+
if (!existsSync10(path)) {
|
|
1734
1776
|
return false;
|
|
1735
1777
|
}
|
|
1736
|
-
const content =
|
|
1778
|
+
const content = readFileSync9(path, "utf8");
|
|
1737
1779
|
if (GITIGNORE_ENV_PATTERN.test(content)) {
|
|
1738
1780
|
return false;
|
|
1739
1781
|
}
|
|
@@ -1747,11 +1789,11 @@ function addGitignoreEnv(projectDir) {
|
|
|
1747
1789
|
var AGENT_ARTIFACT_DIRS = [".factory/", ".goose/", ".uzys-agent-harness/"];
|
|
1748
1790
|
var GITIGNORE_AGENT_ARTIFACT_HEADER = "# agent CLI / harness \uC790\uB3D9 \uC0DD\uC131\uBB3C (auto-added by agent-harness)";
|
|
1749
1791
|
function addGitignoreAgentArtifacts(projectDir) {
|
|
1750
|
-
const path =
|
|
1751
|
-
if (!
|
|
1792
|
+
const path = join10(projectDir, ".gitignore");
|
|
1793
|
+
if (!existsSync10(path)) {
|
|
1752
1794
|
return [];
|
|
1753
1795
|
}
|
|
1754
|
-
const content =
|
|
1796
|
+
const content = readFileSync9(path, "utf8");
|
|
1755
1797
|
const missing = AGENT_ARTIFACT_DIRS.filter((pattern) => {
|
|
1756
1798
|
const lineRegex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\//g, "/")}\\s*$`, "m");
|
|
1757
1799
|
return !lineRegex.test(content);
|
|
@@ -1770,10 +1812,14 @@ ${block}
|
|
|
1770
1812
|
// src/external-installer.ts
|
|
1771
1813
|
init_esm_shims();
|
|
1772
1814
|
import { spawnSync } from "child_process";
|
|
1773
|
-
import { existsSync as
|
|
1815
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync10 } from "fs";
|
|
1774
1816
|
import { homedir as homedir2 } from "os";
|
|
1775
|
-
import { join as
|
|
1817
|
+
import { join as join11 } from "path";
|
|
1776
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
|
+
}
|
|
1777
1823
|
function selectExternalTargets(assets, ctx) {
|
|
1778
1824
|
const conditionPassed = filterApplicableAssets(assets, ctx).filter(
|
|
1779
1825
|
(a) => a.method.kind !== "internal"
|
|
@@ -1848,8 +1894,8 @@ function installOne(asset, ctx) {
|
|
|
1848
1894
|
cwd
|
|
1849
1895
|
);
|
|
1850
1896
|
case "shell-script": {
|
|
1851
|
-
const scriptPath =
|
|
1852
|
-
if (!
|
|
1897
|
+
const scriptPath = join11(ctx.harnessRoot, method.script);
|
|
1898
|
+
if (!existsSync11(scriptPath)) {
|
|
1853
1899
|
return {
|
|
1854
1900
|
asset,
|
|
1855
1901
|
ok: false,
|
|
@@ -1945,8 +1991,17 @@ function installPlugin(asset, spawn, method, scope, cwd) {
|
|
|
1945
1991
|
);
|
|
1946
1992
|
}
|
|
1947
1993
|
function runSpawn(asset, spawn, cmd, args, cwd) {
|
|
1948
|
-
const
|
|
1994
|
+
const timeout = spawnTimeoutFor(asset.method.kind);
|
|
1995
|
+
const result = spawn(cmd, args, spawnOpts(cwd, timeout));
|
|
1949
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
|
+
}
|
|
1950
2005
|
return { asset, ok: false, message: result.error.message };
|
|
1951
2006
|
}
|
|
1952
2007
|
if ((result.status ?? 1) !== 0) {
|
|
@@ -1960,11 +2015,11 @@ function runSpawn(asset, spawn, cmd, args, cwd) {
|
|
|
1960
2015
|
}
|
|
1961
2016
|
return { asset, ok: true };
|
|
1962
2017
|
}
|
|
1963
|
-
function spawnOpts(cwd) {
|
|
2018
|
+
function spawnOpts(cwd, timeout = DEFAULT_SPAWN_TIMEOUT_MS) {
|
|
1964
2019
|
return {
|
|
1965
2020
|
encoding: "utf8",
|
|
1966
2021
|
stdio: "pipe",
|
|
1967
|
-
timeout
|
|
2022
|
+
timeout,
|
|
1968
2023
|
...cwd ? { cwd } : {}
|
|
1969
2024
|
};
|
|
1970
2025
|
}
|
|
@@ -1979,17 +2034,17 @@ function detectVersion(method, spawn) {
|
|
|
1979
2034
|
if (at2 <= 0) return void 0;
|
|
1980
2035
|
const plugin = method.pluginId.slice(0, at2);
|
|
1981
2036
|
const marketplaceShort = method.pluginId.slice(at2 + 1);
|
|
1982
|
-
const cacheBase =
|
|
1983
|
-
if (!
|
|
2037
|
+
const cacheBase = join11(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
|
|
2038
|
+
if (!existsSync11(cacheBase)) return void 0;
|
|
1984
2039
|
const versions = readdirSync2(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
|
|
1985
2040
|
return versions.at(-1);
|
|
1986
2041
|
}
|
|
1987
2042
|
case "npm": {
|
|
1988
2043
|
const npmRoot = getNpmGlobalRoot(spawn);
|
|
1989
2044
|
if (!npmRoot) return void 0;
|
|
1990
|
-
const pkgJson =
|
|
1991
|
-
if (!
|
|
1992
|
-
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"));
|
|
1993
2048
|
return parsed.version;
|
|
1994
2049
|
}
|
|
1995
2050
|
default:
|
|
@@ -2016,7 +2071,7 @@ function getNpmGlobalRoot(spawn) {
|
|
|
2016
2071
|
|
|
2017
2072
|
// src/mcp-merge.ts
|
|
2018
2073
|
init_esm_shims();
|
|
2019
|
-
import { existsSync as
|
|
2074
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
2020
2075
|
function parseTrackMcpMap(raw) {
|
|
2021
2076
|
const rows = [];
|
|
2022
2077
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -2067,15 +2122,15 @@ function mergeMcpServers(base, rows, tracks) {
|
|
|
2067
2122
|
return out;
|
|
2068
2123
|
}
|
|
2069
2124
|
function composeMcpJson(opts) {
|
|
2070
|
-
const base = JSON.parse(
|
|
2071
|
-
const merged = opts.existingPath &&
|
|
2072
|
-
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") : "";
|
|
2073
2128
|
const rows = parseTrackMcpMap(mapRaw);
|
|
2074
2129
|
return mergeMcpServers(merged, rows, opts.tracks);
|
|
2075
2130
|
}
|
|
2076
2131
|
function mergeUserBase(base, existingPath) {
|
|
2077
2132
|
try {
|
|
2078
|
-
const existing = JSON.parse(
|
|
2133
|
+
const existing = JSON.parse(readFileSync11(existingPath, "utf8"));
|
|
2079
2134
|
return {
|
|
2080
2135
|
...base,
|
|
2081
2136
|
mcpServers: { ...base.mcpServers, ...existing.mcpServers }
|
|
@@ -2093,37 +2148,52 @@ function writeMcpJson(path, mcp) {
|
|
|
2093
2148
|
init_esm_shims();
|
|
2094
2149
|
import {
|
|
2095
2150
|
copyFileSync,
|
|
2096
|
-
existsSync as
|
|
2151
|
+
existsSync as existsSync13,
|
|
2097
2152
|
mkdirSync as mkdirSync3,
|
|
2098
2153
|
readdirSync as readdirSync3,
|
|
2099
|
-
readFileSync as
|
|
2154
|
+
readFileSync as readFileSync12,
|
|
2155
|
+
rmdirSync,
|
|
2156
|
+
statSync,
|
|
2100
2157
|
unlinkSync as unlinkSync2,
|
|
2101
2158
|
writeFileSync as writeFileSync5
|
|
2102
2159
|
} from "fs";
|
|
2103
|
-
import { basename as basename4, dirname as dirname3, join as
|
|
2160
|
+
import { basename as basename4, dirname as dirname3, join as join12 } from "path";
|
|
2104
2161
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
2105
|
-
function buildUpdateSpec(projectDir, tracks) {
|
|
2162
|
+
function buildUpdateSpec(projectDir, tracks, only) {
|
|
2106
2163
|
return {
|
|
2107
2164
|
tracks: [...tracks],
|
|
2108
2165
|
options: DEFAULT_OPTIONS,
|
|
2109
2166
|
cli: ["claude"],
|
|
2110
2167
|
projectDir,
|
|
2111
|
-
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] } : {}
|
|
2112
2171
|
};
|
|
2113
2172
|
}
|
|
2114
|
-
function
|
|
2115
|
-
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);
|
|
2116
2182
|
const report = {
|
|
2117
2183
|
updated: {},
|
|
2118
2184
|
pruned: {},
|
|
2119
2185
|
staleHookRefs: [],
|
|
2120
2186
|
claudeMdUpdated: false,
|
|
2187
|
+
backups: [],
|
|
2188
|
+
skippedGroups: only === void 0 ? [] : UPDATE_GROUPS.filter((g) => !only.includes(g)),
|
|
2189
|
+
anchorBackedUp: false,
|
|
2121
2190
|
anchorCreated: false,
|
|
2122
2191
|
rootImportAdded: false,
|
|
2123
2192
|
rootBlockRefreshed: false,
|
|
2124
2193
|
legacyAnchor: null,
|
|
2125
2194
|
skillsBackedUp: [],
|
|
2126
2195
|
skillsSkippedLinks: [],
|
|
2196
|
+
skillsPruned: [],
|
|
2127
2197
|
policyBackedUp: [],
|
|
2128
2198
|
externalUpdated: 0,
|
|
2129
2199
|
externalBackedUp: [],
|
|
@@ -2136,16 +2206,18 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2136
2206
|
restored: [],
|
|
2137
2207
|
needsReinstall: [],
|
|
2138
2208
|
retiredAgents: [],
|
|
2209
|
+
demotedAgents: [],
|
|
2139
2210
|
mcpAllowlistRetired: null
|
|
2140
2211
|
};
|
|
2141
|
-
const fresh = installNewAssets(projectDir, templatesDir);
|
|
2212
|
+
const fresh = installNewAssets(projectDir, templatesDir, wants);
|
|
2142
2213
|
report.installedNew = fresh.installed;
|
|
2143
2214
|
report.restored = fresh.restored;
|
|
2144
2215
|
report.needsReinstall = fresh.needsReinstall;
|
|
2145
2216
|
const policyBase = policyBaseline(projectDir);
|
|
2146
2217
|
for (const { dir, ext } of POLICY_DIRS) {
|
|
2147
|
-
|
|
2148
|
-
const
|
|
2218
|
+
if (!wants(dir === "hooks" ? "hooks" : "rules")) continue;
|
|
2219
|
+
const target = join12(claudeDir, dir);
|
|
2220
|
+
const source = join12(templatesDir, dir);
|
|
2149
2221
|
const label = `.claude/${dir}`;
|
|
2150
2222
|
const ctx = { prefix: dir, baseline: policyBase };
|
|
2151
2223
|
const synced = updateDir(target, source, ext, ctx);
|
|
@@ -2153,29 +2225,38 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2153
2225
|
report.policyBackedUp.push(...synced.backedUp);
|
|
2154
2226
|
report.pruned[label] = pruneOrphans(target, source, ext, ctx);
|
|
2155
2227
|
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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"),
|
|
2160
2235
|
skillBaseline(projectDir),
|
|
2161
2236
|
/* @__PURE__ */ new Date(),
|
|
2162
2237
|
(relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`)
|
|
2163
|
-
);
|
|
2164
|
-
report.updated[".claude/skills"] = skillSync.updated;
|
|
2238
|
+
) : { updated: 0, backedUp: [], skippedLinks: [], foreignOwned: [], pruned: [] };
|
|
2239
|
+
if (wants("skills")) report.updated[".claude/skills"] = skillSync.updated;
|
|
2165
2240
|
report.skillsBackedUp = skillSync.backedUp;
|
|
2166
2241
|
report.skillsSkippedLinks = skillSync.skippedLinks;
|
|
2167
|
-
|
|
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);
|
|
2168
2248
|
const installLog = readInstallLog(projectDir);
|
|
2169
|
-
if (
|
|
2170
|
-
syncHarnessAnchor(projectDir, templatesDir, report);
|
|
2249
|
+
if (existsSync13(claudeDir) && (installLog === null || installLog.spec.cli.includes("claude"))) {
|
|
2250
|
+
if (wants("anchor")) syncHarnessAnchor(projectDir, templatesDir, report);
|
|
2171
2251
|
}
|
|
2172
|
-
const settingsPath =
|
|
2173
|
-
if (
|
|
2252
|
+
const settingsPath = join12(claudeDir, "settings.json");
|
|
2253
|
+
if (wants("hooks") && existsSync13(settingsPath)) {
|
|
2174
2254
|
report.staleHookRefs = cleanStaleHookRefs(settingsPath, claudeDir);
|
|
2175
2255
|
}
|
|
2176
|
-
report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
2256
|
+
if (wants("hooks")) report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
2177
2257
|
report.retiredAgents = staleAgentFiles(claudeDir);
|
|
2178
|
-
|
|
2258
|
+
report.demotedAgents = demotedAgentFiles(claudeDir, installedTracks(projectDir));
|
|
2259
|
+
const external = wants("external") ? refreshExternalCli(projectDir, harnessRoot) : { externalUpdated: 0, externalBackedUp: [], externalForeignOwned: [] };
|
|
2179
2260
|
report.externalUpdated = external.externalUpdated;
|
|
2180
2261
|
report.externalBackedUp = external.externalBackedUp;
|
|
2181
2262
|
const saidBySlotRow = new Set(report.skillsSkippedLinks.map((id) => `.claude/skills/${id}`));
|
|
@@ -2183,12 +2264,13 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2183
2264
|
for (const f of [
|
|
2184
2265
|
...skillSync.foreignOwned,
|
|
2185
2266
|
...fresh.foreignOwned,
|
|
2267
|
+
...newSkills.foreignOwned,
|
|
2186
2268
|
...external.externalForeignOwned
|
|
2187
2269
|
]) {
|
|
2188
2270
|
if (!saidBySlotRow.has(f) && !merged.includes(f)) merged.push(f);
|
|
2189
2271
|
}
|
|
2190
2272
|
report.foreignOwned = merged;
|
|
2191
|
-
const skillRefresh = (deps.refreshSkills ?? refreshExternalSkills)(projectDir);
|
|
2273
|
+
const skillRefresh = wants("external") ? (deps.refreshSkills ?? refreshExternalSkills)(projectDir) : { attempted: 0, refreshed: 0, failed: [], notInCatalog: [], unknown: false };
|
|
2192
2274
|
report.externalSkillsRefreshed = skillRefresh.refreshed;
|
|
2193
2275
|
report.externalSkillsFailed = skillRefresh.failed;
|
|
2194
2276
|
report.externalSkillsNotInCatalog = [
|
|
@@ -2196,20 +2278,31 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2196
2278
|
...staleSkillDirs(claudeDir).filter((id) => !skillRefresh.notInCatalog.includes(id))
|
|
2197
2279
|
];
|
|
2198
2280
|
report.externalSkillsUnknown = skillRefresh.unknown;
|
|
2281
|
+
report.backups = collectRunBackups(projectDir, startedAt);
|
|
2282
|
+
writeBackupList(projectDir, report.backups);
|
|
2199
2283
|
return report;
|
|
2200
2284
|
}
|
|
2201
2285
|
function staleSkillDirs(claudeDir) {
|
|
2202
|
-
const dir =
|
|
2203
|
-
if (!
|
|
2286
|
+
const dir = join12(claudeDir, "skills");
|
|
2287
|
+
if (!existsSync13(dir)) return [];
|
|
2204
2288
|
const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
|
|
2205
2289
|
return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && stale(e2.name)).map((e2) => e2.name);
|
|
2206
2290
|
}
|
|
2207
2291
|
function staleAgentFiles(claudeDir) {
|
|
2208
|
-
const dir =
|
|
2209
|
-
if (!
|
|
2292
|
+
const dir = join12(claudeDir, "agents");
|
|
2293
|
+
if (!existsSync13(dir)) return [];
|
|
2210
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));
|
|
2211
2295
|
}
|
|
2212
|
-
function
|
|
2296
|
+
function demotedAgentFiles(claudeDir, tracks) {
|
|
2297
|
+
if (tracks.length === 0) return [];
|
|
2298
|
+
const dir = join12(claudeDir, "agents");
|
|
2299
|
+
if (!existsSync13(dir)) return [];
|
|
2300
|
+
const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
|
|
2301
|
+
([id]) => id
|
|
2302
|
+
);
|
|
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));
|
|
2304
|
+
}
|
|
2305
|
+
function installNewAssets(projectDir, templatesDir, wants = () => true) {
|
|
2213
2306
|
const installed = [];
|
|
2214
2307
|
const restored = [];
|
|
2215
2308
|
const needsReinstall = [];
|
|
@@ -2219,17 +2312,19 @@ function installNewAssets(projectDir, templatesDir) {
|
|
|
2219
2312
|
const baselineExcluded = new Set(log?.spec.baselineExclude ?? []);
|
|
2220
2313
|
const priorBaseline = policyBaseline(projectDir);
|
|
2221
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;
|
|
2222
2317
|
if (entry.target === HARNESS_ANCHOR_FILE) continue;
|
|
2223
2318
|
if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
|
|
2224
|
-
const target =
|
|
2319
|
+
const target = join12(projectDir, entry.target);
|
|
2225
2320
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
2226
2321
|
if (foreign !== null) {
|
|
2227
2322
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2228
2323
|
continue;
|
|
2229
2324
|
}
|
|
2230
|
-
if (
|
|
2231
|
-
const source =
|
|
2232
|
-
if (!
|
|
2325
|
+
if (existsSync13(target)) continue;
|
|
2326
|
+
const source = join12(templatesDir, entry.source);
|
|
2327
|
+
if (!existsSync13(source)) continue;
|
|
2233
2328
|
if (entry.target.startsWith(".claude/")) {
|
|
2234
2329
|
if (!claudeSelected) continue;
|
|
2235
2330
|
if (entry.target.startsWith(".claude/hooks/") || entry.target === ".claude/settings.json") {
|
|
@@ -2245,6 +2340,77 @@ function installNewAssets(projectDir, templatesDir) {
|
|
|
2245
2340
|
}
|
|
2246
2341
|
return { installed, restored, needsReinstall, foreignOwned };
|
|
2247
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
|
+
}
|
|
2248
2414
|
function trackOnlyFileAssets(tracks) {
|
|
2249
2415
|
const base = { tracks, selectedInternalSkills: [] };
|
|
2250
2416
|
const optedOut = { ...base, withEcc: false, withTauri: false };
|
|
@@ -2253,34 +2419,45 @@ function trackOnlyFileAssets(tracks) {
|
|
|
2253
2419
|
(e2) => e2.type === "file" && e2.applies(optedOut) && e2.applies(optedIn)
|
|
2254
2420
|
);
|
|
2255
2421
|
}
|
|
2256
|
-
function syncHarnessAnchor(projectDir, templatesDir, report) {
|
|
2257
|
-
if (
|
|
2422
|
+
function syncHarnessAnchor(projectDir, templatesDir, report, now = /* @__PURE__ */ new Date()) {
|
|
2423
|
+
if (existsSync13(join12(projectDir, LEGACY_ANCHOR_FILE))) {
|
|
2258
2424
|
report.legacyAnchor = LEGACY_ANCHOR_FILE;
|
|
2259
2425
|
}
|
|
2260
|
-
const templateMd =
|
|
2261
|
-
if (!
|
|
2262
|
-
const anchor =
|
|
2263
|
-
const existed =
|
|
2264
|
-
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);
|
|
2265
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
|
+
}
|
|
2266
2441
|
report.claudeMdUpdated = true;
|
|
2442
|
+
recordAnchorBaseline(projectDir, anchor);
|
|
2267
2443
|
report.rootBlockRefreshed = upsertRootImport(projectDir);
|
|
2268
2444
|
return;
|
|
2269
2445
|
}
|
|
2446
|
+
copyFileSync(templateMd, anchor);
|
|
2270
2447
|
report.anchorCreated = true;
|
|
2271
2448
|
report.rootImportAdded = upsertRootImport(projectDir);
|
|
2272
2449
|
recordAnchorBaseline(projectDir, anchor);
|
|
2273
2450
|
}
|
|
2274
2451
|
function upsertRootImport(projectDir) {
|
|
2275
|
-
const target =
|
|
2276
|
-
const existing =
|
|
2452
|
+
const target = join12(projectDir, "CLAUDE.md");
|
|
2453
|
+
const existing = existsSync13(target) ? readFileSync12(target, "utf8") : null;
|
|
2277
2454
|
const next = upsertHarnessImport(existing, {
|
|
2278
2455
|
projectName: basename4(projectDir),
|
|
2279
2456
|
tracks: installedTracks(projectDir),
|
|
2280
2457
|
// ADR-085 — update 는 선택 목록을 인자로 받지 않는다. `syncSkills` 와 같은 판정을 쓴다:
|
|
2281
2458
|
// `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
|
|
2282
2459
|
continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
|
|
2283
|
-
(id) =>
|
|
2460
|
+
(id) => existsSync13(join12(projectDir, ".claude/skills", id))
|
|
2284
2461
|
)
|
|
2285
2462
|
});
|
|
2286
2463
|
if (next === existing) return false;
|
|
@@ -2300,7 +2477,7 @@ function recordAnchorBaseline(projectDir, anchor) {
|
|
|
2300
2477
|
...log.templates,
|
|
2301
2478
|
rootClaudeMd: {
|
|
2302
2479
|
path: HARNESS_ANCHOR_FILE,
|
|
2303
|
-
sha256: hashContent(
|
|
2480
|
+
sha256: hashContent(readFileSync12(anchor, "utf8"))
|
|
2304
2481
|
}
|
|
2305
2482
|
}
|
|
2306
2483
|
};
|
|
@@ -2342,16 +2519,16 @@ function isHarnessOwned2(ctx, rel, current) {
|
|
|
2342
2519
|
return isHarnessOwned(ctx.baseline, `${ctx.prefix}/${rel}`, current);
|
|
2343
2520
|
}
|
|
2344
2521
|
function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
2345
|
-
if (!
|
|
2522
|
+
if (!existsSync13(target) || !existsSync13(source)) return { updated: 0, backedUp: [] };
|
|
2346
2523
|
let updated = 0;
|
|
2347
2524
|
const backedUp = [];
|
|
2348
2525
|
for (const file of readdirSync3(target)) {
|
|
2349
2526
|
if (!file.endsWith(ext)) continue;
|
|
2350
|
-
const targetFile =
|
|
2351
|
-
const sourceFile =
|
|
2352
|
-
if (!
|
|
2353
|
-
const next =
|
|
2354
|
-
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");
|
|
2355
2532
|
if (current === next) continue;
|
|
2356
2533
|
if (!isHarnessOwned2(ctx, file, current)) {
|
|
2357
2534
|
backupFile(targetFile, now);
|
|
@@ -2363,35 +2540,36 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
|
2363
2540
|
return { updated, backedUp };
|
|
2364
2541
|
}
|
|
2365
2542
|
function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date(), foreignOf) {
|
|
2366
|
-
if (!
|
|
2367
|
-
return { updated: 0, backedUp: [], skippedLinks: [], foreignOwned: [] };
|
|
2543
|
+
if (!existsSync13(targetDir) || !existsSync13(sourceDir))
|
|
2544
|
+
return { updated: 0, backedUp: [], skippedLinks: [], foreignOwned: [], pruned: [] };
|
|
2368
2545
|
let updated = 0;
|
|
2369
2546
|
const backedUp = [];
|
|
2370
2547
|
const skippedLinks = [];
|
|
2371
2548
|
const foreignOwned = [];
|
|
2549
|
+
const pruned = [];
|
|
2372
2550
|
for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
2373
2551
|
if (!skill.isDirectory()) continue;
|
|
2374
|
-
const targetSkill =
|
|
2552
|
+
const targetSkill = join12(targetDir, skill.name);
|
|
2375
2553
|
if (occupiedByNonDirectory(targetSkill)) {
|
|
2376
2554
|
skippedLinks.push(skill.name);
|
|
2377
2555
|
continue;
|
|
2378
2556
|
}
|
|
2379
|
-
if (!
|
|
2380
|
-
for (const rel of listFilesRecursive(
|
|
2381
|
-
const targetFile =
|
|
2557
|
+
if (!existsSync13(targetSkill)) continue;
|
|
2558
|
+
for (const rel of listFilesRecursive(join12(sourceDir, skill.name))) {
|
|
2559
|
+
const targetFile = join12(targetSkill, rel);
|
|
2382
2560
|
const foreign = foreignOf(`${skill.name}/${rel}`);
|
|
2383
2561
|
if (foreign !== null) {
|
|
2384
2562
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2385
2563
|
continue;
|
|
2386
2564
|
}
|
|
2387
|
-
const next =
|
|
2388
|
-
if (!
|
|
2565
|
+
const next = readFileSync12(join12(sourceDir, skill.name, rel), "utf8");
|
|
2566
|
+
if (!existsSync13(targetFile)) {
|
|
2389
2567
|
mkdirSync3(dirname3(targetFile), { recursive: true });
|
|
2390
2568
|
writeFileSync5(targetFile, next);
|
|
2391
2569
|
updated++;
|
|
2392
2570
|
continue;
|
|
2393
2571
|
}
|
|
2394
|
-
const current =
|
|
2572
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2395
2573
|
if (current === next) continue;
|
|
2396
2574
|
const recorded = baseline.get(`${skill.name}/${rel}`);
|
|
2397
2575
|
if (recorded === void 0 || hashContent(current) !== recorded) {
|
|
@@ -2401,8 +2579,23 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
2401
2579
|
writeFileSync5(targetFile, next);
|
|
2402
2580
|
updated++;
|
|
2403
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
|
+
}
|
|
2404
2597
|
}
|
|
2405
|
-
return { updated, backedUp, skippedLinks, foreignOwned };
|
|
2598
|
+
return { updated, backedUp, skippedLinks, foreignOwned, pruned };
|
|
2406
2599
|
}
|
|
2407
2600
|
function skillBaseline(projectDir) {
|
|
2408
2601
|
const log = readInstallLog(projectDir);
|
|
@@ -2412,10 +2605,14 @@ function policyBaseline(projectDir) {
|
|
|
2412
2605
|
const log = readInstallLog(projectDir);
|
|
2413
2606
|
return new Map((log?.policyFiles ?? []).map((f) => [f.path, f.sha256]));
|
|
2414
2607
|
}
|
|
2415
|
-
function refreshPolicyBaseline(projectDir, templatesDir) {
|
|
2608
|
+
function refreshPolicyBaseline(projectDir, templatesDir, dirs) {
|
|
2416
2609
|
const log = readInstallLog(projectDir);
|
|
2417
2610
|
if (!log) return;
|
|
2418
|
-
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
|
+
];
|
|
2419
2616
|
const next = { ...log };
|
|
2420
2617
|
if (policyFiles.length > 0) next.policyFiles = policyFiles;
|
|
2421
2618
|
else delete next.policyFiles;
|
|
@@ -2424,10 +2621,10 @@ function refreshPolicyBaseline(projectDir, templatesDir) {
|
|
|
2424
2621
|
} catch {
|
|
2425
2622
|
}
|
|
2426
2623
|
}
|
|
2427
|
-
function refreshSkillBaseline(projectDir) {
|
|
2624
|
+
function refreshSkillBaseline(projectDir, templatesDir) {
|
|
2428
2625
|
const log = readInstallLog(projectDir);
|
|
2429
2626
|
if (!log) return;
|
|
2430
|
-
const skillFiles = collectSkillHashes(projectDir);
|
|
2627
|
+
const skillFiles = collectSkillHashes(projectDir, templatesDir);
|
|
2431
2628
|
const next = { ...log };
|
|
2432
2629
|
if (skillFiles.length > 0) next.skillFiles = skillFiles;
|
|
2433
2630
|
else delete next.skillFiles;
|
|
@@ -2437,16 +2634,16 @@ function refreshSkillBaseline(projectDir) {
|
|
|
2437
2634
|
}
|
|
2438
2635
|
}
|
|
2439
2636
|
function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
2440
|
-
if (!
|
|
2637
|
+
if (!existsSync13(target) || !existsSync13(source)) return [];
|
|
2441
2638
|
const removed = [];
|
|
2442
2639
|
for (const file of readdirSync3(target)) {
|
|
2443
2640
|
if (!file.endsWith(ext)) continue;
|
|
2444
|
-
const sourceFile =
|
|
2445
|
-
if (!
|
|
2446
|
-
const targetFile =
|
|
2641
|
+
const sourceFile = join12(source, file);
|
|
2642
|
+
if (!existsSync13(sourceFile)) {
|
|
2643
|
+
const targetFile = join12(target, file);
|
|
2447
2644
|
if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
|
|
2448
2645
|
try {
|
|
2449
|
-
const current =
|
|
2646
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2450
2647
|
if (!isHarnessOwned2(ctx, file, current)) backupFile(targetFile, now);
|
|
2451
2648
|
unlinkSync2(targetFile);
|
|
2452
2649
|
removed.push(file);
|
|
@@ -2457,8 +2654,8 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
2457
2654
|
return removed;
|
|
2458
2655
|
}
|
|
2459
2656
|
function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
2460
|
-
const target =
|
|
2461
|
-
if (!
|
|
2657
|
+
const target = join12(projectDir, ".mcp-allowlist");
|
|
2658
|
+
if (!existsSync13(target)) return null;
|
|
2462
2659
|
try {
|
|
2463
2660
|
const backup = backupFile(target, now);
|
|
2464
2661
|
unlinkSync2(target);
|
|
@@ -2470,7 +2667,7 @@ function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
|
2470
2667
|
function cleanStaleHookRefs(settingsPath, claudeDir) {
|
|
2471
2668
|
let settings;
|
|
2472
2669
|
try {
|
|
2473
|
-
settings = JSON.parse(
|
|
2670
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
2474
2671
|
} catch {
|
|
2475
2672
|
return [];
|
|
2476
2673
|
}
|
|
@@ -2497,7 +2694,7 @@ function cleanStaleHookRefs(settingsPath, claudeDir) {
|
|
|
2497
2694
|
function keepHookRef(hook, claudeDir, removed) {
|
|
2498
2695
|
const relPath = projectAnchoredRef(hook?.command ?? "", claudeDir);
|
|
2499
2696
|
if (relPath === void 0) return true;
|
|
2500
|
-
const exists =
|
|
2697
|
+
const exists = existsSync13(join12(claudeDir, relPath));
|
|
2501
2698
|
if (!exists && !removed.includes(relPath)) removed.push(relPath);
|
|
2502
2699
|
return exists;
|
|
2503
2700
|
}
|
|
@@ -2524,14 +2721,14 @@ function projectAnchoredRef(command, claudeDir) {
|
|
|
2524
2721
|
function runInstall(ctx) {
|
|
2525
2722
|
const { harnessRoot, projectDir, spec } = ctx;
|
|
2526
2723
|
const mode = ctx.mode ?? "fresh";
|
|
2527
|
-
const templatesDir =
|
|
2528
|
-
if (!
|
|
2724
|
+
const templatesDir = join13(harnessRoot, "templates");
|
|
2725
|
+
if (!existsSync14(templatesDir)) {
|
|
2529
2726
|
throw new Error(`Templates dir not found: ${templatesDir}`);
|
|
2530
2727
|
}
|
|
2531
|
-
const claudeDir =
|
|
2728
|
+
const claudeDir = join13(projectDir, ".claude");
|
|
2532
2729
|
const previousLog = readInstallLog(projectDir);
|
|
2533
2730
|
const claudeWasSelected = previousLog?.spec.cli.includes("claude") ?? false;
|
|
2534
|
-
if (mode === "update" && !
|
|
2731
|
+
if (mode === "update" && !existsSync14(claudeDir) && (previousLog === null || claudeWasSelected)) {
|
|
2535
2732
|
throw new Error(
|
|
2536
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}`
|
|
2537
2734
|
);
|
|
@@ -2616,9 +2813,9 @@ function runInstall(ctx) {
|
|
|
2616
2813
|
}
|
|
2617
2814
|
function healStaleHookRefs(spec, projectDir) {
|
|
2618
2815
|
if (!spec.cli.includes("claude")) return [];
|
|
2619
|
-
const settingsPath =
|
|
2620
|
-
if (!
|
|
2621
|
-
return cleanStaleHookRefs(settingsPath,
|
|
2816
|
+
const settingsPath = join13(projectDir, ".claude/settings.json");
|
|
2817
|
+
if (!existsSync14(settingsPath)) return [];
|
|
2818
|
+
return cleanStaleHookRefs(settingsPath, join13(projectDir, ".claude"));
|
|
2622
2819
|
}
|
|
2623
2820
|
function resolveBackupPath(ctx, mode, claudeDir) {
|
|
2624
2821
|
const wantBackup = ctx.backup ?? (mode === "update" || mode === "reinstall");
|
|
@@ -2626,7 +2823,13 @@ function resolveBackupPath(ctx, mode, claudeDir) {
|
|
|
2626
2823
|
return mode === "update" ? copyBackupDir(claudeDir) : backupDir(claudeDir);
|
|
2627
2824
|
}
|
|
2628
2825
|
function runUpdateInstall(ctx, templatesDir, backupPath) {
|
|
2629
|
-
const updateReport = runUpdateMode(
|
|
2826
|
+
const updateReport = runUpdateMode(
|
|
2827
|
+
ctx.projectDir,
|
|
2828
|
+
templatesDir,
|
|
2829
|
+
ctx.harnessRoot,
|
|
2830
|
+
{},
|
|
2831
|
+
ctx.spec.updateOnly
|
|
2832
|
+
);
|
|
2630
2833
|
const baseline = {
|
|
2631
2834
|
filesCopied: 0,
|
|
2632
2835
|
dirsCopied: 0,
|
|
@@ -2684,12 +2887,12 @@ function installCliNeutralAssets(manifestSpec, projectDir, templatesDir, baselin
|
|
|
2684
2887
|
continue;
|
|
2685
2888
|
}
|
|
2686
2889
|
if (!isCliNeutralTarget(entry.target)) continue;
|
|
2687
|
-
const source =
|
|
2688
|
-
if (!
|
|
2890
|
+
const source = join13(templatesDir, entry.source);
|
|
2891
|
+
if (!existsSync14(source)) {
|
|
2689
2892
|
result.skipped += 1;
|
|
2690
2893
|
continue;
|
|
2691
2894
|
}
|
|
2692
|
-
copyFile(source,
|
|
2895
|
+
copyFile(source, join13(projectDir, entry.target));
|
|
2693
2896
|
result.filesCopied += 1;
|
|
2694
2897
|
}
|
|
2695
2898
|
return result;
|
|
@@ -2701,9 +2904,9 @@ function backupEditedPolicyFile(entryTarget, target, source, baseline) {
|
|
|
2701
2904
|
if (!POLICY_DIRS.some(({ dir, ext }) => rel.startsWith(`${dir}/`) && rel.endsWith(ext))) {
|
|
2702
2905
|
return null;
|
|
2703
2906
|
}
|
|
2704
|
-
if (!
|
|
2705
|
-
const current =
|
|
2706
|
-
if (current ===
|
|
2907
|
+
if (!existsSync14(target)) return null;
|
|
2908
|
+
const current = readFileSync13(target, "utf-8");
|
|
2909
|
+
if (current === readFileSync13(source, "utf-8")) return null;
|
|
2707
2910
|
const recorded = baseline.get(rel);
|
|
2708
2911
|
if (recorded !== void 0 && recorded === hashContent(current)) return null;
|
|
2709
2912
|
return backupFile(target);
|
|
@@ -2716,14 +2919,14 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2716
2919
|
if (!entry.applies(manifestSpec)) {
|
|
2717
2920
|
continue;
|
|
2718
2921
|
}
|
|
2719
|
-
const target =
|
|
2922
|
+
const target = join13(projectDir, entry.target);
|
|
2720
2923
|
if (isBaselineExcluded(entry.target, baselineExcluded)) {
|
|
2721
2924
|
result.excluded.push(entry.target);
|
|
2722
|
-
if (
|
|
2925
|
+
if (existsSync14(target)) result.excludedOnDisk.push(entry.target);
|
|
2723
2926
|
continue;
|
|
2724
2927
|
}
|
|
2725
|
-
const source =
|
|
2726
|
-
if (!
|
|
2928
|
+
const source = join13(templatesDir, entry.source);
|
|
2929
|
+
if (!existsSync14(source)) {
|
|
2727
2930
|
result.skipped += 1;
|
|
2728
2931
|
continue;
|
|
2729
2932
|
}
|
|
@@ -2734,7 +2937,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2734
2937
|
}
|
|
2735
2938
|
if (entry.type === "file") {
|
|
2736
2939
|
if (entry.target === ".claude/settings.json") {
|
|
2737
|
-
const backup = backupFileIfChanged(target,
|
|
2940
|
+
const backup = backupFileIfChanged(target, readFileSync13(source, "utf-8"));
|
|
2738
2941
|
if (backup) {
|
|
2739
2942
|
result.backups.push(backup);
|
|
2740
2943
|
}
|
|
@@ -2758,8 +2961,8 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2758
2961
|
}
|
|
2759
2962
|
accumulateCategory(result.categories, entry);
|
|
2760
2963
|
}
|
|
2761
|
-
const hookDir =
|
|
2762
|
-
if (
|
|
2964
|
+
const hookDir = join13(projectDir, ".claude/hooks");
|
|
2965
|
+
if (existsSync14(hookDir)) {
|
|
2763
2966
|
chmodHooksSync(hookDir);
|
|
2764
2967
|
}
|
|
2765
2968
|
writeInstalledTracks(projectDir, manifestSpec.tracks);
|
|
@@ -2773,9 +2976,9 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2773
2976
|
return result;
|
|
2774
2977
|
}
|
|
2775
2978
|
function harnessAnchorLog(projectDir) {
|
|
2776
|
-
const anchor =
|
|
2777
|
-
if (!
|
|
2778
|
-
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")) };
|
|
2779
2982
|
}
|
|
2780
2983
|
function writeEnvironmentFiles(projectDir, tracks) {
|
|
2781
2984
|
return {
|
|
@@ -2833,8 +3036,8 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2833
3036
|
claudeDirMovedAside,
|
|
2834
3037
|
rootFiles
|
|
2835
3038
|
);
|
|
2836
|
-
const skillFiles = collectSkillHashes(ctx.projectDir);
|
|
2837
|
-
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"));
|
|
2838
3041
|
const merged = mergeExternalFiles(ctx.projectDir, previousLog?.externalFiles, externalFiles);
|
|
2839
3042
|
writeInstallLog(ctx.projectDir, {
|
|
2840
3043
|
...log,
|
|
@@ -2850,11 +3053,11 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2850
3053
|
}
|
|
2851
3054
|
}
|
|
2852
3055
|
function composeAndWriteMcp(harnessRoot, projectDir, spec) {
|
|
2853
|
-
const mcpPath =
|
|
2854
|
-
const created = !
|
|
3056
|
+
const mcpPath = join13(projectDir, ".mcp.json");
|
|
3057
|
+
const created = !existsSync14(mcpPath);
|
|
2855
3058
|
const composed = composeMcpJson({
|
|
2856
|
-
templateMcpPath:
|
|
2857
|
-
trackMapPath:
|
|
3059
|
+
templateMcpPath: join13(harnessRoot, "templates/mcp.json"),
|
|
3060
|
+
trackMapPath: join13(harnessRoot, "templates/track-mcp-map.tsv"),
|
|
2858
3061
|
existingPath: mcpPath,
|
|
2859
3062
|
tracks: spec.tracks
|
|
2860
3063
|
});
|
|
@@ -2907,15 +3110,15 @@ function accumulateCategory(cats, entry) {
|
|
|
2907
3110
|
}
|
|
2908
3111
|
}
|
|
2909
3112
|
function writeInstalledTracks(projectDir, tracks) {
|
|
2910
|
-
const path =
|
|
3113
|
+
const path = join13(projectDir, ".claude/.installed-tracks");
|
|
2911
3114
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
2912
3115
|
const sorted = [...new Set(tracks)].sort().join("\n");
|
|
2913
3116
|
writeFileSync6(path, `${sorted}
|
|
2914
3117
|
`);
|
|
2915
3118
|
}
|
|
2916
3119
|
function writeRootClaudeMd(projectDir, tracks, continuousSkills) {
|
|
2917
|
-
const target =
|
|
2918
|
-
const existing =
|
|
3120
|
+
const target = join13(projectDir, "CLAUDE.md");
|
|
3121
|
+
const existing = existsSync14(target) ? readFileSync13(target, "utf-8") : null;
|
|
2919
3122
|
const content = upsertHarnessImport(existing, {
|
|
2920
3123
|
projectName: basename5(projectDir),
|
|
2921
3124
|
tracks,
|
|
@@ -2940,6 +3143,8 @@ function listHookFiles(hookDir) {
|
|
|
2940
3143
|
|
|
2941
3144
|
// src/commands/install-render.ts
|
|
2942
3145
|
init_esm_shims();
|
|
3146
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5 } from "fs";
|
|
3147
|
+
import { join as join14 } from "path";
|
|
2943
3148
|
|
|
2944
3149
|
// src/preset-recommend.ts
|
|
2945
3150
|
init_esm_shims();
|
|
@@ -3005,12 +3210,18 @@ function renderInstallHeader(log, spec, mode) {
|
|
|
3005
3210
|
for (const [cat, ids] of groupAssetsByCategory(finalAssets)) {
|
|
3006
3211
|
log(` ${c.dim(`\xB7 ${cat}:`)} ${ids.join(", ")}`);
|
|
3007
3212
|
}
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3213
|
+
if (mode !== "update") {
|
|
3214
|
+
const assetSpec = buildManifestSpec(spec);
|
|
3215
|
+
const cost = formatResidentCostLine(
|
|
3216
|
+
residentCost(
|
|
3217
|
+
buildManifest(assetSpec).filter(
|
|
3218
|
+
(e2) => e2.applies(assetSpec) && landsOnDisk(e2.target, spec.cli)
|
|
3219
|
+
)
|
|
3220
|
+
),
|
|
3221
|
+
summarizeContextCost(finalAssets).unmeasuredCount
|
|
3222
|
+
);
|
|
3223
|
+
if (cost) log(` ${c.dim(`\xB7 ${cost}`)}`);
|
|
3224
|
+
}
|
|
3014
3225
|
}
|
|
3015
3226
|
log("");
|
|
3016
3227
|
}
|
|
@@ -3055,6 +3266,10 @@ function createInstallRenderer(log, spec, verbose) {
|
|
|
3055
3266
|
log(` ${c.bold(`\u2501\u2501 ${CATEGORY_TITLES[asset.category]} \u2501\u2501`)}`);
|
|
3056
3267
|
currentCategory = asset.category;
|
|
3057
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
|
+
}
|
|
3058
3273
|
},
|
|
3059
3274
|
onAssetResult: (result) => {
|
|
3060
3275
|
const meta = result.ok ? formatAssetMeta(result.asset, result.version) : result.message ?? "failed";
|
|
@@ -3064,7 +3279,7 @@ function createInstallRenderer(log, spec, verbose) {
|
|
|
3064
3279
|
};
|
|
3065
3280
|
return { callbacks, phase2HeaderPrinted: () => phase2HeaderPrinted };
|
|
3066
3281
|
}
|
|
3067
|
-
function renderUpdateSummary(log, report) {
|
|
3282
|
+
function renderUpdateSummary(log, spec, report) {
|
|
3068
3283
|
log("");
|
|
3069
3284
|
log(unifiedSection("Summary"));
|
|
3070
3285
|
log("");
|
|
@@ -3074,8 +3289,41 @@ function renderUpdateSummary(log, report) {
|
|
|
3074
3289
|
log(infoRow("BACKUP", shortenPath(report.backup)));
|
|
3075
3290
|
log(infoRow("ROLLBACK", `rm -rf .claude && mv ${shortenPath(report.backup)} .claude`));
|
|
3076
3291
|
}
|
|
3292
|
+
const cost = formatResidentCostLine(
|
|
3293
|
+
residentCost(residentEntriesOnDisk(spec)),
|
|
3294
|
+
summarizeContextCost(finalSelectedAssets(spec.tracks, spec.userOverride)).unmeasuredCount
|
|
3295
|
+
);
|
|
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
|
+
}
|
|
3077
3312
|
log("");
|
|
3078
3313
|
}
|
|
3314
|
+
function residentEntriesOnDisk(spec) {
|
|
3315
|
+
const assetSpec = buildManifestSpec(spec);
|
|
3316
|
+
const planned = buildManifest(assetSpec).filter((e2) => e2.applies(assetSpec));
|
|
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) => ({
|
|
3319
|
+
source: `agents/${e2.name}`,
|
|
3320
|
+
target: `.claude/agents/${e2.name}`,
|
|
3321
|
+
// 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 descriptor 도,
|
|
3322
|
+
// 배포판에 더는 없는 에이전트도 실제로 상주하는 것은 이쪽이다.
|
|
3323
|
+
file: join14(agentsDir, e2.name)
|
|
3324
|
+
})) : [];
|
|
3325
|
+
return [...planned.filter((e2) => !e2.target.startsWith(".claude/agents/")), ...onDisk];
|
|
3326
|
+
}
|
|
3079
3327
|
function renderCliArtifacts(log, spec, report) {
|
|
3080
3328
|
const hasArtifacts = Boolean(report.codex || report.opencode || report.antigravity);
|
|
3081
3329
|
const cliSelected = targetsInclude(spec.cli, "codex") || targetsInclude(spec.cli, "opencode") || targetsInclude(spec.cli, "antigravity");
|
|
@@ -3280,9 +3528,27 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3280
3528
|
log(assetRow("skip", `${dir} orphan prune`, `${removed.length} removed`));
|
|
3281
3529
|
}
|
|
3282
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
|
+
}
|
|
3283
3540
|
if (baseline.updateMode.claudeMdUpdated) {
|
|
3284
3541
|
log(assetRow("success", HARNESS_ANCHOR_FILE, "refreshed from template"));
|
|
3285
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
|
+
}
|
|
3286
3552
|
if (baseline.updateMode.anchorCreated) {
|
|
3287
3553
|
log(assetRow("success", HARNESS_ANCHOR_FILE, "created from template (anchor migration)"));
|
|
3288
3554
|
}
|
|
@@ -3316,6 +3582,15 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3316
3582
|
)
|
|
3317
3583
|
);
|
|
3318
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
|
+
}
|
|
3319
3594
|
if (baseline.updateMode.skillsSkippedLinks.length > 0) {
|
|
3320
3595
|
log(
|
|
3321
3596
|
assetRow(
|
|
@@ -3353,6 +3628,16 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3353
3628
|
)
|
|
3354
3629
|
);
|
|
3355
3630
|
}
|
|
3631
|
+
for (const [id, pattern] of TRACK_AGENTS) {
|
|
3632
|
+
if (!baseline.updateMode.demotedAgents.includes(id)) continue;
|
|
3633
|
+
log(
|
|
3634
|
+
assetRow(
|
|
3635
|
+
"skip",
|
|
3636
|
+
"agents",
|
|
3637
|
+
`${id} \xB7 \uC774 \uD2B8\uB799\uC5D0\uC11C\uB294 \uB354 \uC774\uC0C1 \uC124\uCE58\uD558\uC9C0 \uC54A\uB294\uB2E4 \u2014 ${pattern.split("|").join(" \xB7 ")} \uD2B8\uB799 \uC804\uC6A9 \xB7 .claude/agents/${id}.md \uB97C \uC9C0\uC6CC\uB3C4 \uB41C\uB2E4`
|
|
3638
|
+
)
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3356
3641
|
if (baseline.updateMode.policyBackedUp.length > 0) {
|
|
3357
3642
|
log(
|
|
3358
3643
|
assetRow(
|
|
@@ -3735,7 +4020,7 @@ function executeSpec(spec, deps = {}) {
|
|
|
3735
4020
|
return;
|
|
3736
4021
|
}
|
|
3737
4022
|
if (report.updateMode) {
|
|
3738
|
-
renderUpdateSummary(log, report);
|
|
4023
|
+
renderUpdateSummary(log, spec, report);
|
|
3739
4024
|
return;
|
|
3740
4025
|
}
|
|
3741
4026
|
if (renderer.phase2HeaderPrinted()) {
|
|
@@ -3800,8 +4085,8 @@ function registerInstallCommand(cli2) {
|
|
|
3800
4085
|
|
|
3801
4086
|
// src/commands/list.ts
|
|
3802
4087
|
init_esm_shims();
|
|
3803
|
-
import { existsSync as
|
|
3804
|
-
import { join as
|
|
4088
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
4089
|
+
import { join as join15, resolve as resolve3 } from "path";
|
|
3805
4090
|
function listAction(options = {}, deps = {}) {
|
|
3806
4091
|
const log = deps.log ?? console.log;
|
|
3807
4092
|
const err = deps.err ?? console.error;
|
|
@@ -3864,8 +4149,8 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3864
4149
|
const rows = [` ${c.dim(dirs.join(" "))}`];
|
|
3865
4150
|
const rootMd = log.templates.rootClaudeMd;
|
|
3866
4151
|
if (rootMd) {
|
|
3867
|
-
const path =
|
|
3868
|
-
const modified =
|
|
4152
|
+
const path = join15(projectDir, rootMd.path);
|
|
4153
|
+
const modified = existsSync16(path) && hashContent(readFileSync14(path, "utf8")) !== rootMd.sha256;
|
|
3869
4154
|
rows.push(
|
|
3870
4155
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
3871
4156
|
);
|
|
@@ -3873,7 +4158,7 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3873
4158
|
return rows;
|
|
3874
4159
|
}
|
|
3875
4160
|
function formatRootFileRows(rootFiles, projectDir) {
|
|
3876
|
-
const present = rootFiles.filter((f) =>
|
|
4161
|
+
const present = rootFiles.filter((f) => existsSync16(join15(projectDir, f.path)));
|
|
3877
4162
|
const width = Math.max(0, ...present.map((f) => f.path.length));
|
|
3878
4163
|
return present.map(
|
|
3879
4164
|
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(f.change === "created" ? "\uC0DD\uC131" : "\uBCD1\uD569")} ${c.dim(f.notes.join(" / "))}`
|
|
@@ -3891,15 +4176,15 @@ function registerListCommand(cli2) {
|
|
|
3891
4176
|
init_esm_shims();
|
|
3892
4177
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3893
4178
|
import {
|
|
3894
|
-
existsSync as
|
|
4179
|
+
existsSync as existsSync17,
|
|
3895
4180
|
lstatSync as lstatSync2,
|
|
3896
|
-
readdirSync as
|
|
3897
|
-
readFileSync as
|
|
3898
|
-
rmdirSync,
|
|
4181
|
+
readdirSync as readdirSync6,
|
|
4182
|
+
readFileSync as readFileSync15,
|
|
4183
|
+
rmdirSync as rmdirSync2,
|
|
3899
4184
|
rmSync,
|
|
3900
4185
|
writeFileSync as writeFileSync7
|
|
3901
4186
|
} from "fs";
|
|
3902
|
-
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";
|
|
3903
4188
|
|
|
3904
4189
|
// src/uninstall-interactive.ts
|
|
3905
4190
|
init_esm_shims();
|
|
@@ -5139,7 +5424,7 @@ function settleLog(ctx, io) {
|
|
|
5139
5424
|
return true;
|
|
5140
5425
|
}
|
|
5141
5426
|
} else {
|
|
5142
|
-
io.rm(
|
|
5427
|
+
io.rm(join16(projectDir, INSTALL_LOG_DIR));
|
|
5143
5428
|
io.rm(legacyInstallLogPath(projectDir));
|
|
5144
5429
|
io.log(` ${status.success("install log removed")}`);
|
|
5145
5430
|
}
|
|
@@ -5189,7 +5474,7 @@ function advisoryLines(plan, projectDir, rootFiles) {
|
|
|
5189
5474
|
return lines;
|
|
5190
5475
|
}
|
|
5191
5476
|
function rootFileAdvisoryLines(rootFiles, projectDir) {
|
|
5192
|
-
const present = rootFiles.filter((f) =>
|
|
5477
|
+
const present = rootFiles.filter((f) => existsSync17(join16(projectDir, f.path)));
|
|
5193
5478
|
if (present.length === 0) return [];
|
|
5194
5479
|
return [
|
|
5195
5480
|
"",
|
|
@@ -5290,16 +5575,16 @@ function buildGlobalAdvisoryCmd(asset) {
|
|
|
5290
5575
|
}
|
|
5291
5576
|
}
|
|
5292
5577
|
function removeTemplates(log, projectDir, rm) {
|
|
5293
|
-
rm(
|
|
5294
|
-
if (log.templates.codexDir) rm(
|
|
5295
|
-
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));
|
|
5296
5581
|
const external = removeExternalFiles(log, projectDir, rm);
|
|
5297
5582
|
const importStripped = stripRootImport(projectDir);
|
|
5298
5583
|
const rootMd = log.templates.rootClaudeMd;
|
|
5299
5584
|
if (rootMd) {
|
|
5300
5585
|
if (rootClaudeMdModified(log, projectDir))
|
|
5301
5586
|
return { rootClaudeMdKept: true, importStripped, external };
|
|
5302
|
-
rm(
|
|
5587
|
+
rm(join16(projectDir, rootMd.path));
|
|
5303
5588
|
}
|
|
5304
5589
|
return { rootClaudeMdKept: false, importStripped, external };
|
|
5305
5590
|
}
|
|
@@ -5307,17 +5592,17 @@ function removeExternalFiles(log, projectDir, rm) {
|
|
|
5307
5592
|
const removed = [];
|
|
5308
5593
|
const kept = [];
|
|
5309
5594
|
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
5310
|
-
const abs =
|
|
5311
|
-
if (!
|
|
5595
|
+
const abs = join16(projectDir, path);
|
|
5596
|
+
if (!existsSync17(abs)) continue;
|
|
5312
5597
|
if (!lstatSync2(abs).isFile()) continue;
|
|
5313
|
-
if (hashContent(
|
|
5598
|
+
if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
|
|
5314
5599
|
kept.push(path);
|
|
5315
5600
|
continue;
|
|
5316
5601
|
}
|
|
5317
5602
|
rm(abs);
|
|
5318
5603
|
removed.push(path);
|
|
5319
5604
|
}
|
|
5320
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(
|
|
5605
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(join16(projectDir, path)));
|
|
5321
5606
|
return { removed, kept };
|
|
5322
5607
|
}
|
|
5323
5608
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
@@ -5326,8 +5611,8 @@ function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
|
5326
5611
|
const rel = relative2(projectDir, cursor);
|
|
5327
5612
|
if (rel === "" || rel.startsWith(`..${sep2}`) || rel === "..") return;
|
|
5328
5613
|
try {
|
|
5329
|
-
if (
|
|
5330
|
-
|
|
5614
|
+
if (readdirSync6(cursor).length > 0) return;
|
|
5615
|
+
rmdirSync2(cursor);
|
|
5331
5616
|
} catch {
|
|
5332
5617
|
return;
|
|
5333
5618
|
}
|
|
@@ -5339,7 +5624,7 @@ function stripRootImport(projectDir) {
|
|
|
5339
5624
|
const stripped = current === null ? null : stripHarnessImport(current);
|
|
5340
5625
|
if (stripped === null) return false;
|
|
5341
5626
|
try {
|
|
5342
|
-
writeFileSync7(
|
|
5627
|
+
writeFileSync7(join16(projectDir, "CLAUDE.md"), stripped, "utf8");
|
|
5343
5628
|
return true;
|
|
5344
5629
|
} catch {
|
|
5345
5630
|
return false;
|
|
@@ -5349,9 +5634,9 @@ function previewExternalLines(installLog, projectDir) {
|
|
|
5349
5634
|
const lines = [];
|
|
5350
5635
|
let removable = 0;
|
|
5351
5636
|
for (const { path, sha256 } of installLog.externalFiles ?? []) {
|
|
5352
|
-
const abs =
|
|
5353
|
-
if (!
|
|
5354
|
-
if (hashContent(
|
|
5637
|
+
const abs = join16(projectDir, path);
|
|
5638
|
+
if (!existsSync17(abs)) continue;
|
|
5639
|
+
if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
|
|
5355
5640
|
lines.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
|
|
5356
5641
|
continue;
|
|
5357
5642
|
}
|
|
@@ -5377,10 +5662,10 @@ function hasRootImport(projectDir) {
|
|
|
5377
5662
|
return current !== null && stripHarnessImport(current) !== null;
|
|
5378
5663
|
}
|
|
5379
5664
|
function readRootClaudeMd(projectDir) {
|
|
5380
|
-
const path =
|
|
5381
|
-
if (!
|
|
5665
|
+
const path = join16(projectDir, "CLAUDE.md");
|
|
5666
|
+
if (!existsSync17(path)) return null;
|
|
5382
5667
|
try {
|
|
5383
|
-
return
|
|
5668
|
+
return readFileSync15(path, "utf8");
|
|
5384
5669
|
} catch {
|
|
5385
5670
|
return null;
|
|
5386
5671
|
}
|
|
@@ -5388,9 +5673,9 @@ function readRootClaudeMd(projectDir) {
|
|
|
5388
5673
|
function rootClaudeMdModified(log, projectDir) {
|
|
5389
5674
|
const rootMd = log.templates.rootClaudeMd;
|
|
5390
5675
|
if (!rootMd) return false;
|
|
5391
|
-
const path =
|
|
5392
|
-
if (!
|
|
5393
|
-
return hashContent(
|
|
5676
|
+
const path = join16(projectDir, rootMd.path);
|
|
5677
|
+
if (!existsSync17(path)) return false;
|
|
5678
|
+
return hashContent(readFileSync15(path, "utf8")) !== rootMd.sha256;
|
|
5394
5679
|
}
|
|
5395
5680
|
function formatTemplateList(log) {
|
|
5396
5681
|
const items = [log.templates.claudeDir];
|
|
@@ -5402,7 +5687,7 @@ function defaultSpawn2(cmd, args) {
|
|
|
5402
5687
|
return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
|
|
5403
5688
|
}
|
|
5404
5689
|
function defaultRm(path) {
|
|
5405
|
-
if (
|
|
5690
|
+
if (existsSync17(path)) {
|
|
5406
5691
|
rmSync(path, { recursive: true, force: true });
|
|
5407
5692
|
}
|
|
5408
5693
|
}
|
|
@@ -5450,8 +5735,8 @@ import { resolve as resolve5 } from "path";
|
|
|
5450
5735
|
|
|
5451
5736
|
// src/state.ts
|
|
5452
5737
|
init_esm_shims();
|
|
5453
|
-
import { existsSync as
|
|
5454
|
-
import { join as
|
|
5738
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
5739
|
+
import { join as join17 } from "path";
|
|
5455
5740
|
var META_FILE = ".claude/.installed-tracks";
|
|
5456
5741
|
var LEGACY_SIGNATURES = [
|
|
5457
5742
|
{ rule: "htmx.md", track: "ssr-htmx" },
|
|
@@ -5461,11 +5746,11 @@ var LEGACY_SIGNATURES = [
|
|
|
5461
5746
|
{ rule: "cli-development.md", track: "tooling" }
|
|
5462
5747
|
];
|
|
5463
5748
|
function detectInstallState(projectDir) {
|
|
5464
|
-
const claudeDir =
|
|
5465
|
-
const hasClaudeDir =
|
|
5749
|
+
const claudeDir = join17(projectDir, ".claude");
|
|
5750
|
+
const hasClaudeDir = existsSync18(claudeDir);
|
|
5466
5751
|
if (hasClaudeDir) {
|
|
5467
|
-
const metaPath =
|
|
5468
|
-
if (
|
|
5752
|
+
const metaPath = join17(projectDir, META_FILE);
|
|
5753
|
+
if (existsSync18(metaPath)) {
|
|
5469
5754
|
const tracks2 = readMetafile(metaPath);
|
|
5470
5755
|
return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
|
|
5471
5756
|
}
|
|
@@ -5487,7 +5772,7 @@ function tracksFromLog(tracks) {
|
|
|
5487
5772
|
return [...new Set(tracks.filter(isTrack))].sort();
|
|
5488
5773
|
}
|
|
5489
5774
|
function readMetafile(path) {
|
|
5490
|
-
const raw =
|
|
5775
|
+
const raw = readFileSync16(path, "utf8");
|
|
5491
5776
|
const seen = /* @__PURE__ */ new Set();
|
|
5492
5777
|
for (const line of raw.split(/\s+/)) {
|
|
5493
5778
|
const trimmed = line.trim();
|
|
@@ -5498,13 +5783,13 @@ function readMetafile(path) {
|
|
|
5498
5783
|
return [...seen].sort();
|
|
5499
5784
|
}
|
|
5500
5785
|
function inferFromLegacySignatures(projectDir) {
|
|
5501
|
-
const rulesDir =
|
|
5502
|
-
if (!
|
|
5786
|
+
const rulesDir = join17(projectDir, ".claude/rules");
|
|
5787
|
+
if (!existsSync18(rulesDir)) {
|
|
5503
5788
|
return [];
|
|
5504
5789
|
}
|
|
5505
5790
|
const found = /* @__PURE__ */ new Set();
|
|
5506
5791
|
for (const sig of LEGACY_SIGNATURES) {
|
|
5507
|
-
if (
|
|
5792
|
+
if (existsSync18(join17(rulesDir, sig.rule))) {
|
|
5508
5793
|
found.add(sig.track);
|
|
5509
5794
|
}
|
|
5510
5795
|
}
|
|
@@ -5526,8 +5811,21 @@ function updateAction(options = {}, deps = {}) {
|
|
|
5526
5811
|
exit(1);
|
|
5527
5812
|
return;
|
|
5528
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
|
+
}
|
|
5529
5822
|
log(c.dim(`Updating installed harness files in ${projectDir}`));
|
|
5530
|
-
execute(buildUpdateSpec(projectDir, state.tracks), {
|
|
5823
|
+
execute(buildUpdateSpec(projectDir, state.tracks, parsed.groups), {
|
|
5824
|
+
log,
|
|
5825
|
+
err,
|
|
5826
|
+
exit,
|
|
5827
|
+
mode: "update"
|
|
5828
|
+
});
|
|
5531
5829
|
}
|
|
5532
5830
|
function registerUpdateCommand(cli2) {
|
|
5533
5831
|
cli2.command(
|
|
@@ -5535,7 +5833,10 @@ function registerUpdateCommand(cli2) {
|
|
|
5535
5833
|
"Refresh installed policy files (rules / agents / commands / hooks / skills)"
|
|
5536
5834
|
).option("--project-dir <path>", "[Project] Target project directory", {
|
|
5537
5835
|
default: process.cwd()
|
|
5538
|
-
}).
|
|
5836
|
+
}).option(
|
|
5837
|
+
"--only <group>",
|
|
5838
|
+
`[Scope] Update only this group (repeatable): ${UPDATE_GROUPS.join(" | ")}`
|
|
5839
|
+
).action((options) => {
|
|
5539
5840
|
updateAction(options);
|
|
5540
5841
|
});
|
|
5541
5842
|
}
|
|
@@ -5650,19 +5951,6 @@ var INSTALL_TARGET_PAGES = [
|
|
|
5650
5951
|
{ label: "Visual & Media (Slides \xB7 Diagrams \xB7 Motion \xB7 Video)", cats: ["visual-media"] },
|
|
5651
5952
|
{ label: "Workflow & ECC Suite", cats: ["workflow", "ecc-suite"] }
|
|
5652
5953
|
];
|
|
5653
|
-
var DEV_METHOD_BUNDLE_VALUE = "bundle:dev-method";
|
|
5654
|
-
var DEV_METHOD_BUNDLE_CATEGORY = "workflow";
|
|
5655
|
-
var bundleMemberValues = () => DEV_METHOD_SKILL_IDS.map((id) => `asset:${id}`);
|
|
5656
|
-
function collapseDevMethodBundle(ids) {
|
|
5657
|
-
const members = new Set(bundleMemberValues());
|
|
5658
|
-
const rest = ids.filter((v2) => !members.has(v2));
|
|
5659
|
-
const anyMember = ids.some((v2) => members.has(v2));
|
|
5660
|
-
return anyMember ? [...rest, DEV_METHOD_BUNDLE_VALUE] : rest;
|
|
5661
|
-
}
|
|
5662
|
-
function expandDevMethodBundle(ids) {
|
|
5663
|
-
const rest = ids.filter((v2) => v2 !== DEV_METHOD_BUNDLE_VALUE);
|
|
5664
|
-
return ids.includes(DEV_METHOD_BUNDLE_VALUE) ? [...rest, ...bundleMemberValues()] : rest;
|
|
5665
|
-
}
|
|
5666
5954
|
var BASELINE_TITLES = {
|
|
5667
5955
|
rules: "Rules \u2014 \uC0C1\uC2DC \uC0C1\uC8FC\uD558\uB294 \uB300\uC6D0\uCE59 (4 CLI \uC804\uBD80\uC5D0 \uC124\uCE58)",
|
|
5668
5956
|
hooks: "Hooks \u2014 \uACB0\uC815\uB860\uC801 \uAC00\uB4DC (\uD574\uC81C\uD558\uBA74 settings.json \uBC30\uC120\uB3C4 \uD568\uAED8 \uBE60\uC9C4\uB2E4)",
|
|
@@ -5676,7 +5964,8 @@ function buildPageGroups(cats, initialSet, installedSet = /* @__PURE__ */ new Se
|
|
|
5676
5964
|
for (const kind of baselineKinds) {
|
|
5677
5965
|
const items = baselineTargets.filter((t) => t.kind === kind).map((t) => ({
|
|
5678
5966
|
value: t.id,
|
|
5679
|
-
label: ` ${t.name}${installedMark(t.id)}
|
|
5967
|
+
label: ` ${t.name}${installedMark(t.id)}`,
|
|
5968
|
+
...t.hint ? { hint: t.hint } : {}
|
|
5680
5969
|
}));
|
|
5681
5970
|
if (items.length === 0) continue;
|
|
5682
5971
|
groups[BASELINE_TITLES[kind]] = items;
|
|
@@ -5692,16 +5981,10 @@ function buildPageGroups(cats, initialSet, installedSet = /* @__PURE__ */ new Se
|
|
|
5692
5981
|
hint: o.hint
|
|
5693
5982
|
});
|
|
5694
5983
|
}
|
|
5695
|
-
if (cat === DEV_METHOD_BUNDLE_CATEGORY) {
|
|
5696
|
-
items.push({
|
|
5697
|
-
value: DEV_METHOD_BUNDLE_VALUE,
|
|
5698
|
-
label: ` uzys \uD558\uB124\uC2A4 \uBC29\uBC95\uB860 ${DEV_METHOD_SKILL_IDS.length}\uC885 [uzys] \u2605 official${installedMark(DEV_METHOD_BUNDLE_VALUE)}`,
|
|
5699
|
-
hint: DEV_METHOD_SKILL_IDS.join(", ")
|
|
5700
|
-
});
|
|
5701
|
-
}
|
|
5702
5984
|
const tierOrder = { official: 0, vetted: 1, experimental: 2 };
|
|
5703
|
-
const
|
|
5704
|
-
|
|
5985
|
+
const catAssets = [...EXTERNAL_ASSETS.filter((x) => x.category === cat)].sort(
|
|
5986
|
+
(a, b2) => tierOrder[assetTrustTier(a.id)] - tierOrder[assetTrustTier(b2.id)]
|
|
5987
|
+
);
|
|
5705
5988
|
for (const a of catAssets) {
|
|
5706
5989
|
const tier = assetTrustTier(a.id);
|
|
5707
5990
|
const badge = tier === "official" ? " \u2605 official" : tier === "experimental" ? " \u26A0 experimental (opt-in)" : "";
|
|
@@ -5807,6 +6090,42 @@ var defaultPrompts = {
|
|
|
5807
6090
|
});
|
|
5808
6091
|
return q(result) ? null : result;
|
|
5809
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
|
+
},
|
|
5810
6129
|
confirmInstall: async (summary) => {
|
|
5811
6130
|
const result = await ue({
|
|
5812
6131
|
message: `${summary}
|
|
@@ -5818,14 +6137,11 @@ Proceed?`,
|
|
|
5818
6137
|
},
|
|
5819
6138
|
selectInstallTargets: async (initialChecked, step, recap) => {
|
|
5820
6139
|
const pages = INSTALL_TARGET_PAGES;
|
|
5821
|
-
const baselineTargets = listBaselineTargets({ tracks: recap?.tracks ?? [] });
|
|
5822
|
-
const
|
|
5823
|
-
const
|
|
5824
|
-
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);
|
|
5825
6143
|
const recapLine = recap ? `Tracks: ${recap.tracks.join(", ")} \xB7 CLIs: ${recap.cli.join(", ")}` : "";
|
|
5826
|
-
const installedSet = new Set(
|
|
5827
|
-
collapseDevMethodBundle(recap?.installed ?? [])
|
|
5828
|
-
);
|
|
6144
|
+
const installedSet = new Set(recap?.installed ?? []);
|
|
5829
6145
|
process.stdout.write("\x1B[?1049h");
|
|
5830
6146
|
let resultIds = null;
|
|
5831
6147
|
let aborted = false;
|
|
@@ -5885,7 +6201,7 @@ Proceed?`,
|
|
|
5885
6201
|
pageIdx++;
|
|
5886
6202
|
}
|
|
5887
6203
|
if (!aborted) {
|
|
5888
|
-
resultIds =
|
|
6204
|
+
resultIds = [...collected];
|
|
5889
6205
|
}
|
|
5890
6206
|
} finally {
|
|
5891
6207
|
process.stdout.write("\x1B[?1049l");
|
|
@@ -5975,11 +6291,15 @@ async function runInteractive(projectDir, deps = {}) {
|
|
|
5975
6291
|
}
|
|
5976
6292
|
if (action === "update") {
|
|
5977
6293
|
mode = "update";
|
|
5978
|
-
const
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
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)}`);
|
|
5983
6303
|
if (!confirmed) {
|
|
5984
6304
|
prompts.outro("Cancelled.");
|
|
5985
6305
|
return { ok: false, reason: "cancelled" };
|
|
@@ -6126,7 +6446,7 @@ function formatSummary(spec) {
|
|
|
6126
6446
|
const cost = formatResidentCostLine(
|
|
6127
6447
|
residentCost(
|
|
6128
6448
|
buildManifest(assetSpec).filter(
|
|
6129
|
-
(e2) => e2.applies(assetSpec) && !isBaselineExcluded(e2.target, baselineExcluded)
|
|
6449
|
+
(e2) => e2.applies(assetSpec) && !isBaselineExcluded(e2.target, baselineExcluded) && landsOnDisk(e2.target, spec.cli)
|
|
6130
6450
|
)
|
|
6131
6451
|
),
|
|
6132
6452
|
summarizeContextCost(finalAssets).unmeasuredCount
|