@uzysjung/agent-harness 26.150.0 → 26.151.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/README.ko.md +1 -1
- package/README.md +1 -1
- package/dist/{chunk-NKBUDHPC.js → chunk-3QBHZUVB.js} +99 -50
- package/dist/chunk-3QBHZUVB.js.map +1 -0
- package/dist/index.js +389 -274
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/templates/CLAUDE.md +145 -164
- package/templates/agents/build-error-resolver.md +1 -1
- package/templates/agents/plan-checker.md +1 -1
- package/templates/agents/reviewer.md +4 -5
- package/templates/antigravity/AGENTS.md.template +3 -23
- package/templates/codex/AGENTS.md.template +5 -56
- package/templates/hooks/protect-files.sh +4 -0
- package/templates/opencode/AGENTS.md.template +4 -52
- package/templates/opencode/opencode.json.template +0 -8
- package/templates/rules/change-management.md +0 -1
- package/templates/rules/cli-development.md +1 -1
- package/templates/rules/doc-governance.md +2 -0
- package/templates/rules/git-policy.md +1 -1
- package/templates/rules/ship-checklist.md +3 -3
- package/templates/rules/test-policy.md +3 -8
- package/templates/settings.json +1 -16
- package/templates/skills/agent-introspection-debugging/SKILL.md +1 -1
- package/templates/skills/audit-harness-fit/README.md +113 -0
- package/templates/skills/audit-harness-fit/SKILL.md +64 -433
- package/templates/skills/audit-harness-fit/evals/scenarios.yaml +222 -0
- package/templates/skills/audit-harness-fit/references/apply.md +66 -0
- package/templates/skills/audit-harness-fit/references/audit.md +160 -0
- package/templates/skills/audit-harness-fit/references/populate.md +74 -0
- package/templates/skills/audit-harness-fit/references/verification.md +123 -0
- package/templates/skills/compaction-handoff/SKILL.md +2 -2
- package/templates/skills/model-orchestration/SKILL.md +7 -0
- package/templates/skills/natural-korean/SKILL.md +45 -0
- package/templates/skills/north-star/references/roadmap-method.md +2 -2
- package/templates/skills/{task-brief → objective-brief}/SKILL.md +17 -16
- package/templates/skills/recurrence-prevention/SKILL.md +16 -16
- package/dist/chunk-NKBUDHPC.js.map +0 -1
- package/templates/agents/code-reviewer.md +0 -237
- package/templates/agents/security-reviewer.md +0 -108
- package/templates/hooks/task-brief-nudge.sh +0 -57
- package/templates/skills/audit-harness-fit/references/official-criteria.md +0 -367
- package/templates/skills/continuous-learning-v2/SKILL.md +0 -361
- package/templates/skills/continuous-learning-v2/agents/observer-loop.sh +0 -362
- package/templates/skills/continuous-learning-v2/agents/observer.md +0 -189
- package/templates/skills/continuous-learning-v2/agents/session-guardian.sh +0 -150
- package/templates/skills/continuous-learning-v2/agents/start-observer.sh +0 -252
- package/templates/skills/continuous-learning-v2/config.json +0 -8
- package/templates/skills/continuous-learning-v2/hooks/observe.sh +0 -585
- package/templates/skills/continuous-learning-v2/scripts/detect-project.sh +0 -322
- package/templates/skills/continuous-learning-v2/scripts/instinct-cli.py +0 -1956
- package/templates/skills/continuous-learning-v2/scripts/lib/homunculus-dir.sh +0 -31
- package/templates/skills/continuous-learning-v2/scripts/migrate-homunculus.sh +0 -68
- package/templates/skills/continuous-learning-v2/scripts/test_parse_instinct.py +0 -1420
- package/templates/skills/humanize-korean/SKILL.md +0 -228
- package/templates/skills/spec-scaling/SKILL.md +0 -89
- package/templates/skills/strategic-compact/SKILL.md +0 -145
- package/templates/skills/strategic-compact/suggest-compact.sh +0 -54
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
CATEGORIES,
|
|
5
5
|
CATEGORY_TITLES,
|
|
6
6
|
CLI_BASES,
|
|
7
|
+
CONTINUOUS_SKILLS,
|
|
7
8
|
DEFAULT_OPTIONS,
|
|
8
9
|
DEV_METHOD_SKILL_IDS,
|
|
9
10
|
EXTERNAL_ASSETS,
|
|
@@ -12,6 +13,9 @@ import {
|
|
|
12
13
|
INSTALL_LOG_DIR,
|
|
13
14
|
INTERNAL_BUNDLED_SKILL_IDS,
|
|
14
15
|
POLICY_DIRS,
|
|
16
|
+
RENAMED_SKILL_IDS,
|
|
17
|
+
RETIRED_AGENT_IDS,
|
|
18
|
+
RETIRED_SKILL_IDS,
|
|
15
19
|
TRACKS,
|
|
16
20
|
__commonJS,
|
|
17
21
|
__toESM,
|
|
@@ -56,8 +60,9 @@ import {
|
|
|
56
60
|
stripHarnessImport,
|
|
57
61
|
summarizeContextCost,
|
|
58
62
|
upsertHarnessImport,
|
|
63
|
+
withContinuousSkillsNote,
|
|
59
64
|
writeInstallLog
|
|
60
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-3QBHZUVB.js";
|
|
61
66
|
|
|
62
67
|
// node_modules/sisteransi/src/index.js
|
|
63
68
|
var require_src = __commonJS({
|
|
@@ -742,7 +747,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
742
747
|
// package.json
|
|
743
748
|
var package_default = {
|
|
744
749
|
name: "@uzysjung/agent-harness",
|
|
745
|
-
version: "26.
|
|
750
|
+
version: "26.151.0",
|
|
746
751
|
description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
|
|
747
752
|
type: "module",
|
|
748
753
|
publishConfig: {
|
|
@@ -980,15 +985,15 @@ function padDisplay(s, width) {
|
|
|
980
985
|
// src/installer.ts
|
|
981
986
|
init_esm_shims();
|
|
982
987
|
import {
|
|
983
|
-
chmodSync as
|
|
984
|
-
existsSync as
|
|
988
|
+
chmodSync as chmodSync3,
|
|
989
|
+
existsSync as existsSync14,
|
|
985
990
|
mkdirSync as mkdirSync4,
|
|
986
991
|
readdirSync as readdirSync4,
|
|
987
|
-
readFileSync as
|
|
992
|
+
readFileSync as readFileSync13,
|
|
988
993
|
unlinkSync as unlinkSync3,
|
|
989
994
|
writeFileSync as writeFileSync6
|
|
990
995
|
} from "fs";
|
|
991
|
-
import { basename as basename5, dirname as dirname4, join as
|
|
996
|
+
import { basename as basename5, dirname as dirname4, join as join13, resolve } from "path";
|
|
992
997
|
|
|
993
998
|
// src/ci-scaffold.ts
|
|
994
999
|
init_esm_shims();
|
|
@@ -1033,8 +1038,8 @@ init_esm_shims();
|
|
|
1033
1038
|
|
|
1034
1039
|
// src/antigravity/transform.ts
|
|
1035
1040
|
init_esm_shims();
|
|
1036
|
-
import { existsSync as
|
|
1037
|
-
import { basename, join as
|
|
1041
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1042
|
+
import { basename, join as join5 } from "path";
|
|
1038
1043
|
|
|
1039
1044
|
// src/codex/agents-md.ts
|
|
1040
1045
|
init_esm_shims();
|
|
@@ -1049,6 +1054,8 @@ function renderAgentsMd(params) {
|
|
|
1049
1054
|
|
|
1050
1055
|
// src/codex/skills.ts
|
|
1051
1056
|
init_esm_shims();
|
|
1057
|
+
import { chmodSync, existsSync as existsSync2, readFileSync } from "fs";
|
|
1058
|
+
import { join as join2 } from "path";
|
|
1052
1059
|
function renderBundledSkill(source) {
|
|
1053
1060
|
const trimmed = source.trimEnd();
|
|
1054
1061
|
const lines = trimmed.split(/\r?\n/);
|
|
@@ -1076,16 +1083,46 @@ ${portBody(body)}
|
|
|
1076
1083
|
function portBody(body) {
|
|
1077
1084
|
return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
|
|
1078
1085
|
}
|
|
1086
|
+
function writeBundledSkillDirs(params) {
|
|
1087
|
+
const { harnessRoot, projectDir, skillIds, writer } = params;
|
|
1088
|
+
const written = [];
|
|
1089
|
+
for (const id of skillIds) {
|
|
1090
|
+
const srcDir = join2(harnessRoot, "templates/skills", id);
|
|
1091
|
+
const srcSkill = join2(srcDir, "SKILL.md");
|
|
1092
|
+
if (!existsSync2(srcSkill)) {
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
const targetDir = join2(projectDir, ".agents", "skills", id);
|
|
1096
|
+
const targetSkill = join2(targetDir, "SKILL.md");
|
|
1097
|
+
if (!writer.write(targetSkill, renderBundledSkill(readFileSync(srcSkill, "utf8")))) {
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
written.push(targetSkill);
|
|
1101
|
+
for (const rel of listFilesRecursive(srcDir)) {
|
|
1102
|
+
if (rel === "SKILL.md") continue;
|
|
1103
|
+
if (rel.split("/").some((segment) => segment.startsWith("."))) continue;
|
|
1104
|
+
const segments = rel.split("/");
|
|
1105
|
+
const raw = readFileSync(join2(srcDir, ...segments), "utf8");
|
|
1106
|
+
const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
|
|
1107
|
+
` : raw;
|
|
1108
|
+
const target = join2(targetDir, ...segments);
|
|
1109
|
+
if (!writer.write(target, ported, { createInRefresh: true })) continue;
|
|
1110
|
+
if (rel.endsWith(".sh")) chmodSync(target, 493);
|
|
1111
|
+
written.push(target);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return written;
|
|
1115
|
+
}
|
|
1079
1116
|
|
|
1080
1117
|
// src/owned-write.ts
|
|
1081
1118
|
init_esm_shims();
|
|
1082
|
-
import { existsSync as
|
|
1119
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1083
1120
|
import { dirname, relative, sep } from "path";
|
|
1084
1121
|
|
|
1085
1122
|
// src/foreign-slot.ts
|
|
1086
1123
|
init_esm_shims();
|
|
1087
1124
|
import { lstatSync } from "fs";
|
|
1088
|
-
import { join as
|
|
1125
|
+
import { join as join3 } from "path";
|
|
1089
1126
|
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
1090
1127
|
function occupiedByNonDirectory(path) {
|
|
1091
1128
|
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
@@ -1094,15 +1131,15 @@ function occupiedByNonDirectory(path) {
|
|
|
1094
1131
|
function foreignOwnedTarget(projectDir, rel) {
|
|
1095
1132
|
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
1096
1133
|
if (slot === void 0) return null;
|
|
1097
|
-
if (occupiedByNonDirectory(
|
|
1134
|
+
if (occupiedByNonDirectory(join3(projectDir, slot))) return slot;
|
|
1098
1135
|
if (rel !== slot) {
|
|
1099
1136
|
const parts = rel.slice(slot.length + 1).split("/");
|
|
1100
1137
|
let cursor = slot;
|
|
1101
1138
|
for (const part of parts.slice(0, -1)) {
|
|
1102
1139
|
cursor = `${cursor}/${part}`;
|
|
1103
|
-
if (occupiedByNonDirectory(
|
|
1140
|
+
if (occupiedByNonDirectory(join3(projectDir, cursor))) return cursor;
|
|
1104
1141
|
}
|
|
1105
|
-
const leaf = lstatSync(
|
|
1142
|
+
const leaf = lstatSync(join3(projectDir, rel), { throwIfNoEntry: false });
|
|
1106
1143
|
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
1107
1144
|
}
|
|
1108
1145
|
return null;
|
|
@@ -1125,13 +1162,13 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1125
1162
|
return false;
|
|
1126
1163
|
}
|
|
1127
1164
|
const digest = hashContent(content);
|
|
1128
|
-
if (!
|
|
1165
|
+
if (!existsSync3(absPath)) {
|
|
1129
1166
|
if (refreshOnly && !opts?.createInRefresh) return false;
|
|
1130
1167
|
mkdirSync(dirname(absPath), { recursive: true });
|
|
1131
1168
|
writeFileSync(absPath, content);
|
|
1132
1169
|
updated++;
|
|
1133
1170
|
} else {
|
|
1134
|
-
const current =
|
|
1171
|
+
const current = readFileSync2(absPath, "utf8");
|
|
1135
1172
|
if (current !== content) {
|
|
1136
1173
|
if (!isHarnessOwned(baseline, rel, current)) {
|
|
1137
1174
|
backupPaths.push(backupFile(absPath, now));
|
|
@@ -1158,8 +1195,8 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1158
1195
|
|
|
1159
1196
|
// src/rules-port.ts
|
|
1160
1197
|
init_esm_shims();
|
|
1161
|
-
import { existsSync as
|
|
1162
|
-
import { join as
|
|
1198
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1199
|
+
import { join as join4 } from "path";
|
|
1163
1200
|
function stripClaudeFrontmatter(source) {
|
|
1164
1201
|
if (!source.startsWith("---\n")) return source.trim();
|
|
1165
1202
|
const end = source.indexOf("\n---", 4);
|
|
@@ -1169,9 +1206,9 @@ function stripClaudeFrontmatter(source) {
|
|
|
1169
1206
|
function portRules(harnessRoot, ruleNames) {
|
|
1170
1207
|
const ported = [];
|
|
1171
1208
|
for (const name of ruleNames) {
|
|
1172
|
-
const src =
|
|
1173
|
-
if (!
|
|
1174
|
-
ported.push({ name, body: stripClaudeFrontmatter(
|
|
1209
|
+
const src = join4(harnessRoot, "templates/rules", `${name}.md`);
|
|
1210
|
+
if (!existsSync4(src)) continue;
|
|
1211
|
+
ported.push({ name, body: stripClaudeFrontmatter(readFileSync3(src, "utf8")) });
|
|
1175
1212
|
}
|
|
1176
1213
|
return ported;
|
|
1177
1214
|
}
|
|
@@ -1191,27 +1228,23 @@ function runAntigravityTransform(params) {
|
|
|
1191
1228
|
refreshOnly
|
|
1192
1229
|
} = params;
|
|
1193
1230
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1194
|
-
const rulesFile = writeRules(harnessRoot, projectDir, writer);
|
|
1231
|
+
const rulesFile = writeRules(harnessRoot, projectDir, writer, selectedInternalSkills);
|
|
1195
1232
|
const antigravityInstalled = rulesFile !== null;
|
|
1196
1233
|
const harnessRuleFiles = [];
|
|
1197
1234
|
for (const rule of portRules(harnessRoot, rules)) {
|
|
1198
|
-
const target =
|
|
1235
|
+
const target = join5(projectDir, ".agents", "rules", `${rule.name}.md`);
|
|
1199
1236
|
if (!writer.write(target, `${rule.body}
|
|
1200
1237
|
`, { createInRefresh: antigravityInstalled })) {
|
|
1201
1238
|
continue;
|
|
1202
1239
|
}
|
|
1203
1240
|
harnessRuleFiles.push(target);
|
|
1204
1241
|
}
|
|
1205
|
-
const skillFiles =
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
const target = join4(projectDir, ".agents", "skills", id, "SKILL.md");
|
|
1212
|
-
if (!writer.write(target, renderBundledSkill(readFileSync3(src, "utf8")))) continue;
|
|
1213
|
-
skillFiles.push(target);
|
|
1214
|
-
}
|
|
1242
|
+
const skillFiles = writeBundledSkillDirs({
|
|
1243
|
+
harnessRoot,
|
|
1244
|
+
projectDir,
|
|
1245
|
+
skillIds: selectedInternalSkills,
|
|
1246
|
+
writer
|
|
1247
|
+
});
|
|
1215
1248
|
return {
|
|
1216
1249
|
rulesFile,
|
|
1217
1250
|
harnessRuleFiles,
|
|
@@ -1219,20 +1252,24 @@ function runAntigravityTransform(params) {
|
|
|
1219
1252
|
ownership: writer.result()
|
|
1220
1253
|
};
|
|
1221
1254
|
}
|
|
1222
|
-
function writeRules(harnessRoot, projectDir, writer) {
|
|
1223
|
-
const claudeMdPath =
|
|
1224
|
-
const templatePath =
|
|
1225
|
-
if (!
|
|
1255
|
+
function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills) {
|
|
1256
|
+
const claudeMdPath = join5(harnessRoot, "templates/CLAUDE.md");
|
|
1257
|
+
const templatePath = join5(harnessRoot, "templates/antigravity/AGENTS.md.template");
|
|
1258
|
+
if (!existsSync5(claudeMdPath) || !existsSync5(templatePath)) {
|
|
1226
1259
|
return null;
|
|
1227
1260
|
}
|
|
1228
|
-
const claudeMd =
|
|
1229
|
-
const template =
|
|
1230
|
-
const target =
|
|
1261
|
+
const claudeMd = readFileSync4(claudeMdPath, "utf8");
|
|
1262
|
+
const template = readFileSync4(templatePath, "utf8");
|
|
1263
|
+
const target = join5(projectDir, ".agents", "rules", "uzys-harness.md");
|
|
1231
1264
|
const rulesOut = renderAgentsMd({
|
|
1232
1265
|
template,
|
|
1233
1266
|
claudeMd,
|
|
1234
1267
|
projectName: basename(projectDir),
|
|
1235
|
-
|
|
1268
|
+
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
1269
|
+
projectContext: withContinuousSkillsNote(
|
|
1270
|
+
renderFillScaffold("antigravity-rule"),
|
|
1271
|
+
selectedInternalSkills
|
|
1272
|
+
)
|
|
1236
1273
|
});
|
|
1237
1274
|
return writer.write(target, rulesOut) ? target : null;
|
|
1238
1275
|
}
|
|
@@ -1240,18 +1277,18 @@ function writeRules(harnessRoot, projectDir, writer) {
|
|
|
1240
1277
|
// src/codex/opt-in.ts
|
|
1241
1278
|
init_esm_shims();
|
|
1242
1279
|
import { homedir } from "os";
|
|
1243
|
-
import { join as
|
|
1280
|
+
import { join as join6 } from "path";
|
|
1244
1281
|
|
|
1245
1282
|
// src/codex/trust-entry.ts
|
|
1246
1283
|
init_esm_shims();
|
|
1247
|
-
import { existsSync as
|
|
1284
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
1248
1285
|
import { dirname as dirname2 } from "path";
|
|
1249
1286
|
var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
|
|
1250
1287
|
function registerTrustEntry(opts) {
|
|
1251
1288
|
const { configPath, projectDir } = opts;
|
|
1252
1289
|
try {
|
|
1253
1290
|
mkdirSync2(dirname2(configPath), { recursive: true });
|
|
1254
|
-
const existing =
|
|
1291
|
+
const existing = existsSync6(configPath) ? readFileSync5(configPath, "utf8") : "";
|
|
1255
1292
|
if (hasTrustEntry(existing, projectDir)) {
|
|
1256
1293
|
return { status: "already-present" };
|
|
1257
1294
|
}
|
|
@@ -1275,8 +1312,8 @@ function hasTrustEntry(configContent, projectDir) {
|
|
|
1275
1312
|
|
|
1276
1313
|
// src/codex/opt-in.ts
|
|
1277
1314
|
function runCodexOptIn(ctx) {
|
|
1278
|
-
const codexHome = ctx.codexHome ??
|
|
1279
|
-
const configPath =
|
|
1315
|
+
const codexHome = ctx.codexHome ?? join6(homedir(), ".codex");
|
|
1316
|
+
const configPath = join6(codexHome, "config.toml");
|
|
1280
1317
|
const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
|
|
1281
1318
|
return {
|
|
1282
1319
|
trustEntry: {
|
|
@@ -1289,8 +1326,8 @@ function runCodexOptIn(ctx) {
|
|
|
1289
1326
|
|
|
1290
1327
|
// src/codex/transform.ts
|
|
1291
1328
|
init_esm_shims();
|
|
1292
|
-
import { chmodSync, existsSync as
|
|
1293
|
-
import { basename as basename2, join as
|
|
1329
|
+
import { chmodSync as chmodSync2, existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
1330
|
+
import { basename as basename2, join as join7 } from "path";
|
|
1294
1331
|
|
|
1295
1332
|
// src/codex/config-toml.ts
|
|
1296
1333
|
init_esm_shims();
|
|
@@ -1367,23 +1404,27 @@ function runCodexTransform(params) {
|
|
|
1367
1404
|
refreshOnly
|
|
1368
1405
|
} = params;
|
|
1369
1406
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1370
|
-
const claudeMd = readRequired(
|
|
1371
|
-
const agentsTemplate = readRequired(
|
|
1372
|
-
const configTemplate = readRequired(
|
|
1407
|
+
const claudeMd = readRequired(join7(harnessRoot, "templates/CLAUDE.md"));
|
|
1408
|
+
const agentsTemplate = readRequired(join7(harnessRoot, "templates/codex/AGENTS.md.template"));
|
|
1409
|
+
const configTemplate = readRequired(join7(harnessRoot, "templates/codex/config.toml.template"));
|
|
1373
1410
|
const projectName = basename2(projectDir);
|
|
1374
|
-
const mcp = readOptionalJson(
|
|
1375
|
-
const agentsMdPath =
|
|
1411
|
+
const mcp = readOptionalJson(join7(harnessRoot, ".mcp.json"));
|
|
1412
|
+
const agentsMdPath = join7(projectDir, "AGENTS.md");
|
|
1376
1413
|
ensureDir(projectDir);
|
|
1377
1414
|
const agentsMdOut = renderAgentsMd({
|
|
1378
1415
|
template: agentsTemplate,
|
|
1379
1416
|
claudeMd,
|
|
1380
1417
|
projectName,
|
|
1381
|
-
|
|
1418
|
+
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
1419
|
+
projectContext: withContinuousSkillsNote(
|
|
1420
|
+
renderFillScaffold("agents-md"),
|
|
1421
|
+
selectedInternalSkills
|
|
1422
|
+
),
|
|
1382
1423
|
// Codex 는 룰 디렉터리가 없다 — 룰이 AGENTS.md 본문에 들어가야 도달한다(§Harness Rules).
|
|
1383
1424
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1384
1425
|
});
|
|
1385
1426
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1386
|
-
const configTomlPath =
|
|
1427
|
+
const configTomlPath = join7(projectDir, ".codex/config.toml");
|
|
1387
1428
|
writer.write(
|
|
1388
1429
|
configTomlPath,
|
|
1389
1430
|
renderConfigToml({
|
|
@@ -1393,29 +1434,25 @@ function runCodexTransform(params) {
|
|
|
1393
1434
|
mcp
|
|
1394
1435
|
})
|
|
1395
1436
|
);
|
|
1396
|
-
const hookDir =
|
|
1437
|
+
const hookDir = join7(projectDir, ".codex/hooks");
|
|
1397
1438
|
const hookFiles = [];
|
|
1398
1439
|
for (const hook of HOOK_NAMES) {
|
|
1399
|
-
const src =
|
|
1400
|
-
if (!
|
|
1440
|
+
const src = join7(harnessRoot, "templates/hooks", `${hook}.sh`);
|
|
1441
|
+
if (!existsSync7(src)) {
|
|
1401
1442
|
continue;
|
|
1402
1443
|
}
|
|
1403
|
-
const ported =
|
|
1404
|
-
const target =
|
|
1444
|
+
const ported = readFileSync6(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
|
|
1445
|
+
const target = join7(hookDir, `${hook}.sh`);
|
|
1405
1446
|
if (!writer.write(target, ported)) continue;
|
|
1406
|
-
|
|
1447
|
+
chmodSync2(target, 493);
|
|
1407
1448
|
hookFiles.push(target);
|
|
1408
1449
|
}
|
|
1409
|
-
const skillFiles =
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
const target = join6(projectDir, ".agents", "skills", id, "SKILL.md");
|
|
1416
|
-
if (!writer.write(target, renderBundledSkill(readFileSync5(src, "utf8")))) continue;
|
|
1417
|
-
skillFiles.push(target);
|
|
1418
|
-
}
|
|
1450
|
+
const skillFiles = writeBundledSkillDirs({
|
|
1451
|
+
harnessRoot,
|
|
1452
|
+
projectDir,
|
|
1453
|
+
skillIds: selectedInternalSkills,
|
|
1454
|
+
writer
|
|
1455
|
+
});
|
|
1419
1456
|
return {
|
|
1420
1457
|
agentsMdPath,
|
|
1421
1458
|
configTomlPath,
|
|
@@ -1425,17 +1462,17 @@ function runCodexTransform(params) {
|
|
|
1425
1462
|
};
|
|
1426
1463
|
}
|
|
1427
1464
|
function readRequired(path) {
|
|
1428
|
-
if (!
|
|
1465
|
+
if (!existsSync7(path)) {
|
|
1429
1466
|
throw new Error(`Codex transform: required source missing: ${path}`);
|
|
1430
1467
|
}
|
|
1431
|
-
return
|
|
1468
|
+
return readFileSync6(path, "utf8");
|
|
1432
1469
|
}
|
|
1433
1470
|
function readOptionalJson(path) {
|
|
1434
|
-
if (!
|
|
1471
|
+
if (!existsSync7(path)) {
|
|
1435
1472
|
return null;
|
|
1436
1473
|
}
|
|
1437
1474
|
try {
|
|
1438
|
-
return JSON.parse(
|
|
1475
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
1439
1476
|
} catch {
|
|
1440
1477
|
return null;
|
|
1441
1478
|
}
|
|
@@ -1443,8 +1480,8 @@ function readOptionalJson(path) {
|
|
|
1443
1480
|
|
|
1444
1481
|
// src/opencode/transform.ts
|
|
1445
1482
|
init_esm_shims();
|
|
1446
|
-
import { existsSync as
|
|
1447
|
-
import { basename as basename3, join as
|
|
1483
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, unlinkSync } from "fs";
|
|
1484
|
+
import { basename as basename3, join as join8 } from "path";
|
|
1448
1485
|
|
|
1449
1486
|
// src/opencode/agents-md.ts
|
|
1450
1487
|
init_esm_shims();
|
|
@@ -1487,46 +1524,46 @@ function runOpencodeTransform(params) {
|
|
|
1487
1524
|
refreshOnly
|
|
1488
1525
|
} = params;
|
|
1489
1526
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1490
|
-
const claudeMd = readRequired2(
|
|
1491
|
-
const agentsTemplate = readRequired2(
|
|
1527
|
+
const claudeMd = readRequired2(join8(harnessRoot, "templates/CLAUDE.md"));
|
|
1528
|
+
const agentsTemplate = readRequired2(join8(harnessRoot, "templates/opencode/AGENTS.md.template"));
|
|
1492
1529
|
const opencodeTemplate = readRequired2(
|
|
1493
|
-
|
|
1530
|
+
join8(harnessRoot, "templates/opencode/opencode.json.template")
|
|
1494
1531
|
);
|
|
1495
1532
|
const projectName = basename3(projectDir);
|
|
1496
|
-
const mcp = readOptionalJson2(
|
|
1533
|
+
const mcp = readOptionalJson2(join8(harnessRoot, ".mcp.json"));
|
|
1497
1534
|
ensureDir(projectDir);
|
|
1498
|
-
const agentsMdPath =
|
|
1535
|
+
const agentsMdPath = join8(projectDir, "AGENTS.md");
|
|
1499
1536
|
const agentsMdOut = renderAgentsMd2({
|
|
1500
1537
|
template: agentsTemplate,
|
|
1501
1538
|
claudeMd,
|
|
1502
1539
|
projectName,
|
|
1503
|
-
|
|
1540
|
+
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
1541
|
+
projectContext: withContinuousSkillsNote(
|
|
1542
|
+
renderFillScaffold("agents-md"),
|
|
1543
|
+
selectedInternalSkills
|
|
1544
|
+
),
|
|
1504
1545
|
// codex 와 **같은 파일**(프로젝트 루트 `AGENTS.md`)이다. 두 transform 이 서로 다른 본문을
|
|
1505
1546
|
// 쓰면 나중에 도는 쪽이 앞선 쪽을 덮어써, codex+opencode 조합에서 룰이 통째로 사라진다
|
|
1506
1547
|
// (독립 검증 C-1 실측). 같은 내용을 쓰면 순서가 결과를 바꾸지 않는다.
|
|
1507
1548
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1508
1549
|
});
|
|
1509
1550
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1510
|
-
const opencodeJsonPath =
|
|
1551
|
+
const opencodeJsonPath = join8(projectDir, "opencode.json");
|
|
1511
1552
|
writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
|
|
1512
|
-
const skillFiles =
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
const target = join7(projectDir, ".agents", "skills", id, "SKILL.md");
|
|
1519
|
-
if (!writer.write(target, renderBundledSkill(readFileSync6(src, "utf8")))) continue;
|
|
1520
|
-
skillFiles.push(target);
|
|
1521
|
-
}
|
|
1553
|
+
const skillFiles = writeBundledSkillDirs({
|
|
1554
|
+
harnessRoot,
|
|
1555
|
+
projectDir,
|
|
1556
|
+
skillIds: selectedInternalSkills,
|
|
1557
|
+
writer
|
|
1558
|
+
});
|
|
1522
1559
|
const retiredCommands = [];
|
|
1523
|
-
const cmdDir =
|
|
1524
|
-
if (
|
|
1560
|
+
const cmdDir = join8(projectDir, ".opencode/commands");
|
|
1561
|
+
if (existsSync8(cmdDir)) {
|
|
1525
1562
|
for (const entry of readdirSync(cmdDir)) {
|
|
1526
1563
|
if (!entry.endsWith(".md")) continue;
|
|
1527
1564
|
const id = entry.slice(0, -3);
|
|
1528
|
-
if (!
|
|
1529
|
-
const victim =
|
|
1565
|
+
if (!existsSync8(join8(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
|
|
1566
|
+
const victim = join8(cmdDir, entry);
|
|
1530
1567
|
try {
|
|
1531
1568
|
backupFile(victim);
|
|
1532
1569
|
unlinkSync(victim);
|
|
@@ -1544,17 +1581,17 @@ function runOpencodeTransform(params) {
|
|
|
1544
1581
|
};
|
|
1545
1582
|
}
|
|
1546
1583
|
function readRequired2(path) {
|
|
1547
|
-
if (!
|
|
1584
|
+
if (!existsSync8(path)) {
|
|
1548
1585
|
throw new Error(`OpenCode transform: required source missing: ${path}`);
|
|
1549
1586
|
}
|
|
1550
|
-
return
|
|
1587
|
+
return readFileSync7(path, "utf8");
|
|
1551
1588
|
}
|
|
1552
1589
|
function readOptionalJson2(path) {
|
|
1553
|
-
if (!
|
|
1590
|
+
if (!existsSync8(path)) {
|
|
1554
1591
|
return null;
|
|
1555
1592
|
}
|
|
1556
1593
|
try {
|
|
1557
|
-
return JSON.parse(
|
|
1594
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1558
1595
|
} catch {
|
|
1559
1596
|
return null;
|
|
1560
1597
|
}
|
|
@@ -1646,8 +1683,8 @@ function runCliTransforms(params) {
|
|
|
1646
1683
|
|
|
1647
1684
|
// src/env-files.ts
|
|
1648
1685
|
init_esm_shims();
|
|
1649
|
-
import { appendFileSync, existsSync as
|
|
1650
|
-
import { join as
|
|
1686
|
+
import { appendFileSync, existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
1687
|
+
import { join as join9 } from "path";
|
|
1651
1688
|
var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
|
|
1652
1689
|
# Copy to .env (gitignored) and fill in values: cp .env.example .env
|
|
1653
1690
|
|
|
@@ -1684,19 +1721,19 @@ function writeEnvExample(projectDir, tracks) {
|
|
|
1684
1721
|
if (!tracks.some((t) => ENV_EXAMPLE_TRACKS.includes(t))) {
|
|
1685
1722
|
return false;
|
|
1686
1723
|
}
|
|
1687
|
-
const path =
|
|
1688
|
-
if (
|
|
1724
|
+
const path = join9(projectDir, ".env.example");
|
|
1725
|
+
if (existsSync9(path)) {
|
|
1689
1726
|
return false;
|
|
1690
1727
|
}
|
|
1691
1728
|
writeFileSync3(path, ENV_EXAMPLE_BODY);
|
|
1692
1729
|
return true;
|
|
1693
1730
|
}
|
|
1694
1731
|
function addGitignoreEnv(projectDir) {
|
|
1695
|
-
const path =
|
|
1696
|
-
if (!
|
|
1732
|
+
const path = join9(projectDir, ".gitignore");
|
|
1733
|
+
if (!existsSync9(path)) {
|
|
1697
1734
|
return false;
|
|
1698
1735
|
}
|
|
1699
|
-
const content =
|
|
1736
|
+
const content = readFileSync8(path, "utf8");
|
|
1700
1737
|
if (GITIGNORE_ENV_PATTERN.test(content)) {
|
|
1701
1738
|
return false;
|
|
1702
1739
|
}
|
|
@@ -1710,11 +1747,11 @@ function addGitignoreEnv(projectDir) {
|
|
|
1710
1747
|
var AGENT_ARTIFACT_DIRS = [".factory/", ".goose/", ".uzys-agent-harness/"];
|
|
1711
1748
|
var GITIGNORE_AGENT_ARTIFACT_HEADER = "# agent CLI / harness \uC790\uB3D9 \uC0DD\uC131\uBB3C (auto-added by agent-harness)";
|
|
1712
1749
|
function addGitignoreAgentArtifacts(projectDir) {
|
|
1713
|
-
const path =
|
|
1714
|
-
if (!
|
|
1750
|
+
const path = join9(projectDir, ".gitignore");
|
|
1751
|
+
if (!existsSync9(path)) {
|
|
1715
1752
|
return [];
|
|
1716
1753
|
}
|
|
1717
|
-
const content =
|
|
1754
|
+
const content = readFileSync8(path, "utf8");
|
|
1718
1755
|
const missing = AGENT_ARTIFACT_DIRS.filter((pattern) => {
|
|
1719
1756
|
const lineRegex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\//g, "/")}\\s*$`, "m");
|
|
1720
1757
|
return !lineRegex.test(content);
|
|
@@ -1733,9 +1770,9 @@ ${block}
|
|
|
1733
1770
|
// src/external-installer.ts
|
|
1734
1771
|
init_esm_shims();
|
|
1735
1772
|
import { spawnSync } from "child_process";
|
|
1736
|
-
import { existsSync as
|
|
1773
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
|
|
1737
1774
|
import { homedir as homedir2 } from "os";
|
|
1738
|
-
import { join as
|
|
1775
|
+
import { join as join10 } from "path";
|
|
1739
1776
|
var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
|
|
1740
1777
|
function selectExternalTargets(assets, ctx) {
|
|
1741
1778
|
const conditionPassed = filterApplicableAssets(assets, ctx).filter(
|
|
@@ -1811,8 +1848,8 @@ function installOne(asset, ctx) {
|
|
|
1811
1848
|
cwd
|
|
1812
1849
|
);
|
|
1813
1850
|
case "shell-script": {
|
|
1814
|
-
const scriptPath =
|
|
1815
|
-
if (!
|
|
1851
|
+
const scriptPath = join10(ctx.harnessRoot, method.script);
|
|
1852
|
+
if (!existsSync10(scriptPath)) {
|
|
1816
1853
|
return {
|
|
1817
1854
|
asset,
|
|
1818
1855
|
ok: false,
|
|
@@ -1942,17 +1979,17 @@ function detectVersion(method, spawn) {
|
|
|
1942
1979
|
if (at2 <= 0) return void 0;
|
|
1943
1980
|
const plugin = method.pluginId.slice(0, at2);
|
|
1944
1981
|
const marketplaceShort = method.pluginId.slice(at2 + 1);
|
|
1945
|
-
const cacheBase =
|
|
1946
|
-
if (!
|
|
1982
|
+
const cacheBase = join10(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
|
|
1983
|
+
if (!existsSync10(cacheBase)) return void 0;
|
|
1947
1984
|
const versions = readdirSync2(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
|
|
1948
1985
|
return versions.at(-1);
|
|
1949
1986
|
}
|
|
1950
1987
|
case "npm": {
|
|
1951
1988
|
const npmRoot = getNpmGlobalRoot(spawn);
|
|
1952
1989
|
if (!npmRoot) return void 0;
|
|
1953
|
-
const pkgJson =
|
|
1954
|
-
if (!
|
|
1955
|
-
const parsed = JSON.parse(
|
|
1990
|
+
const pkgJson = join10(npmRoot, method.pkg, "package.json");
|
|
1991
|
+
if (!existsSync10(pkgJson)) return void 0;
|
|
1992
|
+
const parsed = JSON.parse(readFileSync9(pkgJson, "utf8"));
|
|
1956
1993
|
return parsed.version;
|
|
1957
1994
|
}
|
|
1958
1995
|
default:
|
|
@@ -1979,7 +2016,7 @@ function getNpmGlobalRoot(spawn) {
|
|
|
1979
2016
|
|
|
1980
2017
|
// src/mcp-merge.ts
|
|
1981
2018
|
init_esm_shims();
|
|
1982
|
-
import { existsSync as
|
|
2019
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
1983
2020
|
function parseTrackMcpMap(raw) {
|
|
1984
2021
|
const rows = [];
|
|
1985
2022
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -2030,15 +2067,15 @@ function mergeMcpServers(base, rows, tracks) {
|
|
|
2030
2067
|
return out;
|
|
2031
2068
|
}
|
|
2032
2069
|
function composeMcpJson(opts) {
|
|
2033
|
-
const base = JSON.parse(
|
|
2034
|
-
const merged = opts.existingPath &&
|
|
2035
|
-
const mapRaw =
|
|
2070
|
+
const base = JSON.parse(readFileSync10(opts.templateMcpPath, "utf8"));
|
|
2071
|
+
const merged = opts.existingPath && existsSync11(opts.existingPath) ? mergeUserBase(base, opts.existingPath) : base;
|
|
2072
|
+
const mapRaw = existsSync11(opts.trackMapPath) ? readFileSync10(opts.trackMapPath, "utf8") : "";
|
|
2036
2073
|
const rows = parseTrackMcpMap(mapRaw);
|
|
2037
2074
|
return mergeMcpServers(merged, rows, opts.tracks);
|
|
2038
2075
|
}
|
|
2039
2076
|
function mergeUserBase(base, existingPath) {
|
|
2040
2077
|
try {
|
|
2041
|
-
const existing = JSON.parse(
|
|
2078
|
+
const existing = JSON.parse(readFileSync10(existingPath, "utf8"));
|
|
2042
2079
|
return {
|
|
2043
2080
|
...base,
|
|
2044
2081
|
mcpServers: { ...base.mcpServers, ...existing.mcpServers }
|
|
@@ -2054,8 +2091,8 @@ function writeMcpJson(path, mcp) {
|
|
|
2054
2091
|
|
|
2055
2092
|
// src/superseded.ts
|
|
2056
2093
|
init_esm_shims();
|
|
2057
|
-
import { existsSync as
|
|
2058
|
-
import { join as
|
|
2094
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
2095
|
+
import { join as join11 } from "path";
|
|
2059
2096
|
function sameTracks(a, b2) {
|
|
2060
2097
|
if (a.length !== b2.length) return false;
|
|
2061
2098
|
const sa = [...a].sort();
|
|
@@ -2079,9 +2116,9 @@ function findSuperseded(projectDir, spec, previous) {
|
|
|
2079
2116
|
if (!id) continue;
|
|
2080
2117
|
const key = policyKey(entry.target.slice(".claude/".length));
|
|
2081
2118
|
if (!key) continue;
|
|
2082
|
-
const abs =
|
|
2083
|
-
if (!
|
|
2084
|
-
if (!isHarnessOwned(baseline, key,
|
|
2119
|
+
const abs = join11(projectDir, entry.target);
|
|
2120
|
+
if (!existsSync12(abs)) continue;
|
|
2121
|
+
if (!isHarnessOwned(baseline, key, readFileSync11(abs, "utf-8"))) continue;
|
|
2085
2122
|
out.push({ target: entry.target, id, source: entry.source });
|
|
2086
2123
|
}
|
|
2087
2124
|
return out;
|
|
@@ -2091,14 +2128,14 @@ function findSuperseded(projectDir, spec, previous) {
|
|
|
2091
2128
|
init_esm_shims();
|
|
2092
2129
|
import {
|
|
2093
2130
|
copyFileSync,
|
|
2094
|
-
existsSync as
|
|
2131
|
+
existsSync as existsSync13,
|
|
2095
2132
|
mkdirSync as mkdirSync3,
|
|
2096
2133
|
readdirSync as readdirSync3,
|
|
2097
|
-
readFileSync as
|
|
2134
|
+
readFileSync as readFileSync12,
|
|
2098
2135
|
unlinkSync as unlinkSync2,
|
|
2099
2136
|
writeFileSync as writeFileSync5
|
|
2100
2137
|
} from "fs";
|
|
2101
|
-
import { basename as basename4, dirname as dirname3, join as
|
|
2138
|
+
import { basename as basename4, dirname as dirname3, join as join12 } from "path";
|
|
2102
2139
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
2103
2140
|
function buildUpdateSpec(projectDir, tracks) {
|
|
2104
2141
|
return {
|
|
@@ -2110,7 +2147,7 @@ function buildUpdateSpec(projectDir, tracks) {
|
|
|
2110
2147
|
};
|
|
2111
2148
|
}
|
|
2112
2149
|
function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
2113
|
-
const claudeDir =
|
|
2150
|
+
const claudeDir = join12(projectDir, ".claude");
|
|
2114
2151
|
const report = {
|
|
2115
2152
|
updated: {},
|
|
2116
2153
|
pruned: {},
|
|
@@ -2118,6 +2155,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2118
2155
|
claudeMdUpdated: false,
|
|
2119
2156
|
anchorCreated: false,
|
|
2120
2157
|
rootImportAdded: false,
|
|
2158
|
+
rootBlockRefreshed: false,
|
|
2121
2159
|
legacyAnchor: null,
|
|
2122
2160
|
skillsBackedUp: [],
|
|
2123
2161
|
skillsSkippedLinks: [],
|
|
@@ -2132,6 +2170,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2132
2170
|
installedNew: [],
|
|
2133
2171
|
restored: [],
|
|
2134
2172
|
needsReinstall: [],
|
|
2173
|
+
retiredAgents: [],
|
|
2135
2174
|
mcpAllowlistRetired: null
|
|
2136
2175
|
};
|
|
2137
2176
|
const fresh = installNewAssets(projectDir, templatesDir);
|
|
@@ -2140,8 +2179,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2140
2179
|
report.needsReinstall = fresh.needsReinstall;
|
|
2141
2180
|
const policyBase = policyBaseline(projectDir);
|
|
2142
2181
|
for (const { dir, ext } of POLICY_DIRS) {
|
|
2143
|
-
const target =
|
|
2144
|
-
const source =
|
|
2182
|
+
const target = join12(claudeDir, dir);
|
|
2183
|
+
const source = join12(templatesDir, dir);
|
|
2145
2184
|
const label = `.claude/${dir}`;
|
|
2146
2185
|
const ctx = { prefix: dir, baseline: policyBase };
|
|
2147
2186
|
const synced = updateDir(target, source, ext, ctx);
|
|
@@ -2151,8 +2190,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2151
2190
|
}
|
|
2152
2191
|
refreshPolicyBaseline(projectDir, templatesDir);
|
|
2153
2192
|
const skillSync = syncSkills(
|
|
2154
|
-
|
|
2155
|
-
|
|
2193
|
+
join12(claudeDir, "skills"),
|
|
2194
|
+
join12(templatesDir, "skills"),
|
|
2156
2195
|
skillBaseline(projectDir),
|
|
2157
2196
|
/* @__PURE__ */ new Date(),
|
|
2158
2197
|
(relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`)
|
|
@@ -2162,14 +2201,15 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2162
2201
|
report.skillsSkippedLinks = skillSync.skippedLinks;
|
|
2163
2202
|
refreshSkillBaseline(projectDir);
|
|
2164
2203
|
const installLog = readInstallLog(projectDir);
|
|
2165
|
-
if (
|
|
2204
|
+
if (existsSync13(claudeDir) && (installLog === null || installLog.spec.cli.includes("claude"))) {
|
|
2166
2205
|
syncHarnessAnchor(projectDir, templatesDir, report);
|
|
2167
2206
|
}
|
|
2168
|
-
const settingsPath =
|
|
2169
|
-
if (
|
|
2207
|
+
const settingsPath = join12(claudeDir, "settings.json");
|
|
2208
|
+
if (existsSync13(settingsPath)) {
|
|
2170
2209
|
report.staleHookRefs = cleanStaleHookRefs(settingsPath, claudeDir);
|
|
2171
2210
|
}
|
|
2172
2211
|
report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
2212
|
+
report.retiredAgents = staleAgentFiles(claudeDir);
|
|
2173
2213
|
const external = refreshExternalCli(projectDir, harnessRoot);
|
|
2174
2214
|
report.externalUpdated = external.externalUpdated;
|
|
2175
2215
|
report.externalBackedUp = external.externalBackedUp;
|
|
@@ -2186,10 +2226,24 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2186
2226
|
const skillRefresh = (deps.refreshSkills ?? refreshExternalSkills)(projectDir);
|
|
2187
2227
|
report.externalSkillsRefreshed = skillRefresh.refreshed;
|
|
2188
2228
|
report.externalSkillsFailed = skillRefresh.failed;
|
|
2189
|
-
report.externalSkillsNotInCatalog =
|
|
2229
|
+
report.externalSkillsNotInCatalog = [
|
|
2230
|
+
...skillRefresh.notInCatalog,
|
|
2231
|
+
...staleSkillDirs(claudeDir).filter((id) => !skillRefresh.notInCatalog.includes(id))
|
|
2232
|
+
];
|
|
2190
2233
|
report.externalSkillsUnknown = skillRefresh.unknown;
|
|
2191
2234
|
return report;
|
|
2192
2235
|
}
|
|
2236
|
+
function staleSkillDirs(claudeDir) {
|
|
2237
|
+
const dir = join12(claudeDir, "skills");
|
|
2238
|
+
if (!existsSync13(dir)) return [];
|
|
2239
|
+
const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
|
|
2240
|
+
return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && stale(e2.name)).map((e2) => e2.name);
|
|
2241
|
+
}
|
|
2242
|
+
function staleAgentFiles(claudeDir) {
|
|
2243
|
+
const dir = join12(claudeDir, "agents");
|
|
2244
|
+
if (!existsSync13(dir)) return [];
|
|
2245
|
+
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));
|
|
2246
|
+
}
|
|
2193
2247
|
function installNewAssets(projectDir, templatesDir) {
|
|
2194
2248
|
const installed = [];
|
|
2195
2249
|
const restored = [];
|
|
@@ -2202,15 +2256,15 @@ function installNewAssets(projectDir, templatesDir) {
|
|
|
2202
2256
|
for (const entry of trackOnlyFileAssets(installedTracks(projectDir))) {
|
|
2203
2257
|
if (entry.target === HARNESS_ANCHOR_FILE) continue;
|
|
2204
2258
|
if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
|
|
2205
|
-
const target =
|
|
2259
|
+
const target = join12(projectDir, entry.target);
|
|
2206
2260
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
2207
2261
|
if (foreign !== null) {
|
|
2208
2262
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2209
2263
|
continue;
|
|
2210
2264
|
}
|
|
2211
|
-
if (
|
|
2212
|
-
const source =
|
|
2213
|
-
if (!
|
|
2265
|
+
if (existsSync13(target)) continue;
|
|
2266
|
+
const source = join12(templatesDir, entry.source);
|
|
2267
|
+
if (!existsSync13(source)) continue;
|
|
2214
2268
|
if (entry.target.startsWith(".claude/")) {
|
|
2215
2269
|
if (!claudeSelected) continue;
|
|
2216
2270
|
if (entry.target.startsWith(".claude/hooks/") || entry.target === ".claude/settings.json") {
|
|
@@ -2235,16 +2289,17 @@ function trackOnlyFileAssets(tracks) {
|
|
|
2235
2289
|
);
|
|
2236
2290
|
}
|
|
2237
2291
|
function syncHarnessAnchor(projectDir, templatesDir, report) {
|
|
2238
|
-
if (
|
|
2292
|
+
if (existsSync13(join12(projectDir, LEGACY_ANCHOR_FILE))) {
|
|
2239
2293
|
report.legacyAnchor = LEGACY_ANCHOR_FILE;
|
|
2240
2294
|
}
|
|
2241
|
-
const templateMd =
|
|
2242
|
-
if (!
|
|
2243
|
-
const anchor =
|
|
2244
|
-
const existed =
|
|
2295
|
+
const templateMd = join12(templatesDir, "CLAUDE.md");
|
|
2296
|
+
if (!existsSync13(templateMd)) return;
|
|
2297
|
+
const anchor = join12(projectDir, HARNESS_ANCHOR_FILE);
|
|
2298
|
+
const existed = existsSync13(anchor);
|
|
2245
2299
|
copyFileSync(templateMd, anchor);
|
|
2246
2300
|
if (existed) {
|
|
2247
2301
|
report.claudeMdUpdated = true;
|
|
2302
|
+
report.rootBlockRefreshed = upsertRootImport(projectDir);
|
|
2248
2303
|
return;
|
|
2249
2304
|
}
|
|
2250
2305
|
report.anchorCreated = true;
|
|
@@ -2252,11 +2307,16 @@ function syncHarnessAnchor(projectDir, templatesDir, report) {
|
|
|
2252
2307
|
recordAnchorBaseline(projectDir, anchor);
|
|
2253
2308
|
}
|
|
2254
2309
|
function upsertRootImport(projectDir) {
|
|
2255
|
-
const target =
|
|
2256
|
-
const existing =
|
|
2310
|
+
const target = join12(projectDir, "CLAUDE.md");
|
|
2311
|
+
const existing = existsSync13(target) ? readFileSync12(target, "utf8") : null;
|
|
2257
2312
|
const next = upsertHarnessImport(existing, {
|
|
2258
2313
|
projectName: basename4(projectDir),
|
|
2259
|
-
tracks: installedTracks(projectDir)
|
|
2314
|
+
tracks: installedTracks(projectDir),
|
|
2315
|
+
// ADR-085 — update 는 선택 목록을 인자로 받지 않는다. `syncSkills` 와 같은 판정을 쓴다:
|
|
2316
|
+
// `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
|
|
2317
|
+
continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
|
|
2318
|
+
(id) => existsSync13(join12(projectDir, ".claude/skills", id))
|
|
2319
|
+
)
|
|
2260
2320
|
});
|
|
2261
2321
|
if (next === existing) return false;
|
|
2262
2322
|
writeFileSync5(target, next);
|
|
@@ -2275,7 +2335,7 @@ function recordAnchorBaseline(projectDir, anchor) {
|
|
|
2275
2335
|
...log.templates,
|
|
2276
2336
|
rootClaudeMd: {
|
|
2277
2337
|
path: HARNESS_ANCHOR_FILE,
|
|
2278
|
-
sha256: hashContent(
|
|
2338
|
+
sha256: hashContent(readFileSync12(anchor, "utf8"))
|
|
2279
2339
|
}
|
|
2280
2340
|
}
|
|
2281
2341
|
};
|
|
@@ -2317,16 +2377,16 @@ function isHarnessOwned2(ctx, rel, current) {
|
|
|
2317
2377
|
return isHarnessOwned(ctx.baseline, `${ctx.prefix}/${rel}`, current);
|
|
2318
2378
|
}
|
|
2319
2379
|
function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
2320
|
-
if (!
|
|
2380
|
+
if (!existsSync13(target) || !existsSync13(source)) return { updated: 0, backedUp: [] };
|
|
2321
2381
|
let updated = 0;
|
|
2322
2382
|
const backedUp = [];
|
|
2323
2383
|
for (const file of readdirSync3(target)) {
|
|
2324
2384
|
if (!file.endsWith(ext)) continue;
|
|
2325
|
-
const targetFile =
|
|
2326
|
-
const sourceFile =
|
|
2327
|
-
if (!
|
|
2328
|
-
const next =
|
|
2329
|
-
const current =
|
|
2385
|
+
const targetFile = join12(target, file);
|
|
2386
|
+
const sourceFile = join12(source, file);
|
|
2387
|
+
if (!existsSync13(sourceFile)) continue;
|
|
2388
|
+
const next = readFileSync12(sourceFile, "utf8");
|
|
2389
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2330
2390
|
if (current === next) continue;
|
|
2331
2391
|
if (!isHarnessOwned2(ctx, file, current)) {
|
|
2332
2392
|
backupFile(targetFile, now);
|
|
@@ -2338,7 +2398,7 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
|
2338
2398
|
return { updated, backedUp };
|
|
2339
2399
|
}
|
|
2340
2400
|
function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date(), foreignOf) {
|
|
2341
|
-
if (!
|
|
2401
|
+
if (!existsSync13(targetDir) || !existsSync13(sourceDir))
|
|
2342
2402
|
return { updated: 0, backedUp: [], skippedLinks: [], foreignOwned: [] };
|
|
2343
2403
|
let updated = 0;
|
|
2344
2404
|
const backedUp = [];
|
|
@@ -2346,27 +2406,27 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
2346
2406
|
const foreignOwned = [];
|
|
2347
2407
|
for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
2348
2408
|
if (!skill.isDirectory()) continue;
|
|
2349
|
-
const targetSkill =
|
|
2409
|
+
const targetSkill = join12(targetDir, skill.name);
|
|
2350
2410
|
if (occupiedByNonDirectory(targetSkill)) {
|
|
2351
2411
|
skippedLinks.push(skill.name);
|
|
2352
2412
|
continue;
|
|
2353
2413
|
}
|
|
2354
|
-
if (!
|
|
2355
|
-
for (const rel of listFilesRecursive(
|
|
2356
|
-
const targetFile =
|
|
2414
|
+
if (!existsSync13(targetSkill)) continue;
|
|
2415
|
+
for (const rel of listFilesRecursive(join12(sourceDir, skill.name))) {
|
|
2416
|
+
const targetFile = join12(targetSkill, rel);
|
|
2357
2417
|
const foreign = foreignOf(`${skill.name}/${rel}`);
|
|
2358
2418
|
if (foreign !== null) {
|
|
2359
2419
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2360
2420
|
continue;
|
|
2361
2421
|
}
|
|
2362
|
-
const next =
|
|
2363
|
-
if (!
|
|
2422
|
+
const next = readFileSync12(join12(sourceDir, skill.name, rel), "utf8");
|
|
2423
|
+
if (!existsSync13(targetFile)) {
|
|
2364
2424
|
mkdirSync3(dirname3(targetFile), { recursive: true });
|
|
2365
2425
|
writeFileSync5(targetFile, next);
|
|
2366
2426
|
updated++;
|
|
2367
2427
|
continue;
|
|
2368
2428
|
}
|
|
2369
|
-
const current =
|
|
2429
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2370
2430
|
if (current === next) continue;
|
|
2371
2431
|
const recorded = baseline.get(`${skill.name}/${rel}`);
|
|
2372
2432
|
if (recorded === void 0 || hashContent(current) !== recorded) {
|
|
@@ -2412,16 +2472,16 @@ function refreshSkillBaseline(projectDir) {
|
|
|
2412
2472
|
}
|
|
2413
2473
|
}
|
|
2414
2474
|
function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
2415
|
-
if (!
|
|
2475
|
+
if (!existsSync13(target) || !existsSync13(source)) return [];
|
|
2416
2476
|
const removed = [];
|
|
2417
2477
|
for (const file of readdirSync3(target)) {
|
|
2418
2478
|
if (!file.endsWith(ext)) continue;
|
|
2419
|
-
const sourceFile =
|
|
2420
|
-
if (!
|
|
2421
|
-
const targetFile =
|
|
2479
|
+
const sourceFile = join12(source, file);
|
|
2480
|
+
if (!existsSync13(sourceFile)) {
|
|
2481
|
+
const targetFile = join12(target, file);
|
|
2422
2482
|
if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
|
|
2423
2483
|
try {
|
|
2424
|
-
const current =
|
|
2484
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2425
2485
|
if (!isHarnessOwned2(ctx, file, current)) backupFile(targetFile, now);
|
|
2426
2486
|
unlinkSync2(targetFile);
|
|
2427
2487
|
removed.push(file);
|
|
@@ -2432,8 +2492,8 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
2432
2492
|
return removed;
|
|
2433
2493
|
}
|
|
2434
2494
|
function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
2435
|
-
const target =
|
|
2436
|
-
if (!
|
|
2495
|
+
const target = join12(projectDir, ".mcp-allowlist");
|
|
2496
|
+
if (!existsSync13(target)) return null;
|
|
2437
2497
|
try {
|
|
2438
2498
|
const backup = backupFile(target, now);
|
|
2439
2499
|
unlinkSync2(target);
|
|
@@ -2445,7 +2505,7 @@ function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
|
2445
2505
|
function cleanStaleHookRefs(settingsPath, claudeDir) {
|
|
2446
2506
|
let settings;
|
|
2447
2507
|
try {
|
|
2448
|
-
settings = JSON.parse(
|
|
2508
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
2449
2509
|
} catch {
|
|
2450
2510
|
return [];
|
|
2451
2511
|
}
|
|
@@ -2472,7 +2532,7 @@ function cleanStaleHookRefs(settingsPath, claudeDir) {
|
|
|
2472
2532
|
function keepHookRef(hook, claudeDir, removed) {
|
|
2473
2533
|
const relPath = projectAnchoredRef(hook?.command ?? "", claudeDir);
|
|
2474
2534
|
if (relPath === void 0) return true;
|
|
2475
|
-
const exists =
|
|
2535
|
+
const exists = existsSync13(join12(claudeDir, relPath));
|
|
2476
2536
|
if (!exists && !removed.includes(relPath)) removed.push(relPath);
|
|
2477
2537
|
return exists;
|
|
2478
2538
|
}
|
|
@@ -2499,14 +2559,14 @@ function projectAnchoredRef(command, claudeDir) {
|
|
|
2499
2559
|
function runInstall(ctx) {
|
|
2500
2560
|
const { harnessRoot, projectDir, spec } = ctx;
|
|
2501
2561
|
const mode = ctx.mode ?? "fresh";
|
|
2502
|
-
const templatesDir =
|
|
2503
|
-
if (!
|
|
2562
|
+
const templatesDir = join13(harnessRoot, "templates");
|
|
2563
|
+
if (!existsSync14(templatesDir)) {
|
|
2504
2564
|
throw new Error(`Templates dir not found: ${templatesDir}`);
|
|
2505
2565
|
}
|
|
2506
|
-
const claudeDir =
|
|
2566
|
+
const claudeDir = join13(projectDir, ".claude");
|
|
2507
2567
|
const previousLog = readInstallLog(projectDir);
|
|
2508
2568
|
const claudeWasSelected = previousLog?.spec.cli.includes("claude") ?? false;
|
|
2509
|
-
if (mode === "update" && !
|
|
2569
|
+
if (mode === "update" && !existsSync14(claudeDir) && (previousLog === null || claudeWasSelected)) {
|
|
2510
2570
|
throw new Error(
|
|
2511
2571
|
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}`
|
|
2512
2572
|
);
|
|
@@ -2600,7 +2660,7 @@ function applySupersededCleanup(spec, projectDir, found) {
|
|
|
2600
2660
|
const kept = [];
|
|
2601
2661
|
for (const asset of found) {
|
|
2602
2662
|
try {
|
|
2603
|
-
unlinkSync3(
|
|
2663
|
+
unlinkSync3(join13(projectDir, asset.target));
|
|
2604
2664
|
removed.push(asset.target);
|
|
2605
2665
|
} catch {
|
|
2606
2666
|
kept.push(asset.target);
|
|
@@ -2610,9 +2670,9 @@ function applySupersededCleanup(spec, projectDir, found) {
|
|
|
2610
2670
|
}
|
|
2611
2671
|
function healStaleHookRefs(spec, projectDir) {
|
|
2612
2672
|
if (!spec.cli.includes("claude")) return [];
|
|
2613
|
-
const settingsPath =
|
|
2614
|
-
if (!
|
|
2615
|
-
return cleanStaleHookRefs(settingsPath,
|
|
2673
|
+
const settingsPath = join13(projectDir, ".claude/settings.json");
|
|
2674
|
+
if (!existsSync14(settingsPath)) return [];
|
|
2675
|
+
return cleanStaleHookRefs(settingsPath, join13(projectDir, ".claude"));
|
|
2616
2676
|
}
|
|
2617
2677
|
function resolveBackupPath(ctx, mode, claudeDir) {
|
|
2618
2678
|
const wantBackup = ctx.backup ?? (mode === "update" || mode === "reinstall");
|
|
@@ -2681,12 +2741,12 @@ function installCliNeutralAssets(manifestSpec, projectDir, templatesDir, baselin
|
|
|
2681
2741
|
continue;
|
|
2682
2742
|
}
|
|
2683
2743
|
if (!isCliNeutralTarget(entry.target)) continue;
|
|
2684
|
-
const source =
|
|
2685
|
-
if (!
|
|
2744
|
+
const source = join13(templatesDir, entry.source);
|
|
2745
|
+
if (!existsSync14(source)) {
|
|
2686
2746
|
result.skipped += 1;
|
|
2687
2747
|
continue;
|
|
2688
2748
|
}
|
|
2689
|
-
copyFile(source,
|
|
2749
|
+
copyFile(source, join13(projectDir, entry.target));
|
|
2690
2750
|
result.filesCopied += 1;
|
|
2691
2751
|
}
|
|
2692
2752
|
return result;
|
|
@@ -2698,9 +2758,9 @@ function backupEditedPolicyFile(entryTarget, target, source, baseline) {
|
|
|
2698
2758
|
if (!POLICY_DIRS.some(({ dir, ext }) => rel.startsWith(`${dir}/`) && rel.endsWith(ext))) {
|
|
2699
2759
|
return null;
|
|
2700
2760
|
}
|
|
2701
|
-
if (!
|
|
2702
|
-
const current =
|
|
2703
|
-
if (current ===
|
|
2761
|
+
if (!existsSync14(target)) return null;
|
|
2762
|
+
const current = readFileSync13(target, "utf-8");
|
|
2763
|
+
if (current === readFileSync13(source, "utf-8")) return null;
|
|
2704
2764
|
const recorded = baseline.get(rel);
|
|
2705
2765
|
if (recorded !== void 0 && recorded === hashContent(current)) return null;
|
|
2706
2766
|
return backupFile(target);
|
|
@@ -2713,14 +2773,14 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2713
2773
|
if (!entry.applies(manifestSpec)) {
|
|
2714
2774
|
continue;
|
|
2715
2775
|
}
|
|
2716
|
-
const target =
|
|
2776
|
+
const target = join13(projectDir, entry.target);
|
|
2717
2777
|
if (isBaselineExcluded(entry.target, baselineExcluded)) {
|
|
2718
2778
|
result.excluded.push(entry.target);
|
|
2719
|
-
if (
|
|
2779
|
+
if (existsSync14(target)) result.excludedOnDisk.push(entry.target);
|
|
2720
2780
|
continue;
|
|
2721
2781
|
}
|
|
2722
|
-
const source =
|
|
2723
|
-
if (!
|
|
2782
|
+
const source = join13(templatesDir, entry.source);
|
|
2783
|
+
if (!existsSync14(source)) {
|
|
2724
2784
|
result.skipped += 1;
|
|
2725
2785
|
continue;
|
|
2726
2786
|
}
|
|
@@ -2731,7 +2791,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2731
2791
|
}
|
|
2732
2792
|
if (entry.type === "file") {
|
|
2733
2793
|
if (entry.target === ".claude/settings.json") {
|
|
2734
|
-
const backup = backupFileIfChanged(target,
|
|
2794
|
+
const backup = backupFileIfChanged(target, readFileSync13(source, "utf-8"));
|
|
2735
2795
|
if (backup) {
|
|
2736
2796
|
result.backups.push(backup);
|
|
2737
2797
|
}
|
|
@@ -2755,20 +2815,24 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2755
2815
|
}
|
|
2756
2816
|
accumulateCategory(result.categories, entry);
|
|
2757
2817
|
}
|
|
2758
|
-
const hookDir =
|
|
2759
|
-
if (
|
|
2818
|
+
const hookDir = join13(projectDir, ".claude/hooks");
|
|
2819
|
+
if (existsSync14(hookDir)) {
|
|
2760
2820
|
chmodHooksSync(hookDir);
|
|
2761
2821
|
}
|
|
2762
2822
|
writeInstalledTracks(projectDir, manifestSpec.tracks);
|
|
2763
|
-
const rootClaudeMd = writeRootClaudeMd(
|
|
2823
|
+
const rootClaudeMd = writeRootClaudeMd(
|
|
2824
|
+
projectDir,
|
|
2825
|
+
manifestSpec.tracks,
|
|
2826
|
+
manifestSpec.selectedInternalSkills ?? []
|
|
2827
|
+
);
|
|
2764
2828
|
result.rootClaudeMd = { tracks: manifestSpec.tracks, created: rootClaudeMd.created };
|
|
2765
2829
|
result.rootClaudeMdLog = harnessAnchorLog(projectDir);
|
|
2766
2830
|
return result;
|
|
2767
2831
|
}
|
|
2768
2832
|
function harnessAnchorLog(projectDir) {
|
|
2769
|
-
const anchor =
|
|
2770
|
-
if (!
|
|
2771
|
-
return { path: HARNESS_ANCHOR_FILE, sha256: hashContent(
|
|
2833
|
+
const anchor = join13(projectDir, HARNESS_ANCHOR_FILE);
|
|
2834
|
+
if (!existsSync14(anchor)) return null;
|
|
2835
|
+
return { path: HARNESS_ANCHOR_FILE, sha256: hashContent(readFileSync13(anchor, "utf-8")) };
|
|
2772
2836
|
}
|
|
2773
2837
|
function writeEnvironmentFiles(projectDir, tracks) {
|
|
2774
2838
|
return {
|
|
@@ -2827,7 +2891,7 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2827
2891
|
rootFiles
|
|
2828
2892
|
);
|
|
2829
2893
|
const skillFiles = collectSkillHashes(ctx.projectDir);
|
|
2830
|
-
const policyFiles = collectPolicyHashes(ctx.projectDir,
|
|
2894
|
+
const policyFiles = collectPolicyHashes(ctx.projectDir, join13(ctx.harnessRoot, "templates"));
|
|
2831
2895
|
const merged = mergeExternalFiles(ctx.projectDir, previousLog?.externalFiles, externalFiles);
|
|
2832
2896
|
writeInstallLog(ctx.projectDir, {
|
|
2833
2897
|
...log,
|
|
@@ -2843,11 +2907,11 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2843
2907
|
}
|
|
2844
2908
|
}
|
|
2845
2909
|
function composeAndWriteMcp(harnessRoot, projectDir, spec) {
|
|
2846
|
-
const mcpPath =
|
|
2847
|
-
const created = !
|
|
2910
|
+
const mcpPath = join13(projectDir, ".mcp.json");
|
|
2911
|
+
const created = !existsSync14(mcpPath);
|
|
2848
2912
|
const composed = composeMcpJson({
|
|
2849
|
-
templateMcpPath:
|
|
2850
|
-
trackMapPath:
|
|
2913
|
+
templateMcpPath: join13(harnessRoot, "templates/mcp.json"),
|
|
2914
|
+
trackMapPath: join13(harnessRoot, "templates/track-mcp-map.tsv"),
|
|
2851
2915
|
existingPath: mcpPath,
|
|
2852
2916
|
tracks: spec.tracks
|
|
2853
2917
|
});
|
|
@@ -2900,16 +2964,20 @@ function accumulateCategory(cats, entry) {
|
|
|
2900
2964
|
}
|
|
2901
2965
|
}
|
|
2902
2966
|
function writeInstalledTracks(projectDir, tracks) {
|
|
2903
|
-
const path =
|
|
2967
|
+
const path = join13(projectDir, ".claude/.installed-tracks");
|
|
2904
2968
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
2905
2969
|
const sorted = [...new Set(tracks)].sort().join("\n");
|
|
2906
2970
|
writeFileSync6(path, `${sorted}
|
|
2907
2971
|
`);
|
|
2908
2972
|
}
|
|
2909
|
-
function writeRootClaudeMd(projectDir, tracks) {
|
|
2910
|
-
const target =
|
|
2911
|
-
const existing =
|
|
2912
|
-
const content = upsertHarnessImport(existing, {
|
|
2973
|
+
function writeRootClaudeMd(projectDir, tracks, continuousSkills) {
|
|
2974
|
+
const target = join13(projectDir, "CLAUDE.md");
|
|
2975
|
+
const existing = existsSync14(target) ? readFileSync13(target, "utf-8") : null;
|
|
2976
|
+
const content = upsertHarnessImport(existing, {
|
|
2977
|
+
projectName: basename5(projectDir),
|
|
2978
|
+
tracks,
|
|
2979
|
+
continuousSkills
|
|
2980
|
+
});
|
|
2913
2981
|
if (content !== existing) {
|
|
2914
2982
|
writeFileSync6(target, content);
|
|
2915
2983
|
}
|
|
@@ -2918,7 +2986,7 @@ function writeRootClaudeMd(projectDir, tracks) {
|
|
|
2918
2986
|
function chmodHooksSync(hookDir) {
|
|
2919
2987
|
for (const file of listHookFiles(hookDir)) {
|
|
2920
2988
|
try {
|
|
2921
|
-
|
|
2989
|
+
chmodSync3(file, 493);
|
|
2922
2990
|
} catch {
|
|
2923
2991
|
}
|
|
2924
2992
|
}
|
|
@@ -3085,8 +3153,8 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
3085
3153
|
log(
|
|
3086
3154
|
assetRow(
|
|
3087
3155
|
"success",
|
|
3088
|
-
".agents/skills/<id>/
|
|
3089
|
-
`${report.codex.skillFiles
|
|
3156
|
+
".agents/skills/<id>/",
|
|
3157
|
+
`${countSkillDirs(report.codex.skillFiles)} skills`
|
|
3090
3158
|
)
|
|
3091
3159
|
);
|
|
3092
3160
|
}
|
|
@@ -3104,7 +3172,7 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
3104
3172
|
assetRow(
|
|
3105
3173
|
"success",
|
|
3106
3174
|
".agents/skills/",
|
|
3107
|
-
`${report.opencode.skillFiles
|
|
3175
|
+
`${countSkillDirs(report.opencode.skillFiles)} dev-method skills (codex\xB7antigravity \uC640 \uAC19\uC740 \uC790\uB9AC)`
|
|
3108
3176
|
)
|
|
3109
3177
|
);
|
|
3110
3178
|
}
|
|
@@ -3126,8 +3194,8 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
3126
3194
|
log(
|
|
3127
3195
|
assetRow(
|
|
3128
3196
|
"success",
|
|
3129
|
-
".agents/skills/<id>/
|
|
3130
|
-
`${report.antigravity.skillFiles
|
|
3197
|
+
".agents/skills/<id>/",
|
|
3198
|
+
`${countSkillDirs(report.antigravity.skillFiles)} skills`
|
|
3131
3199
|
)
|
|
3132
3200
|
);
|
|
3133
3201
|
}
|
|
@@ -3193,15 +3261,25 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
3193
3261
|
log(infoRow("NEXT", `Open ${c.bold(label)} \u2014 installed rules & skills are now active`));
|
|
3194
3262
|
const scaffoldFiles = scaffoldFilesForCli(spec.cli);
|
|
3195
3263
|
if (scaffoldFiles.length > 0) {
|
|
3264
|
+
const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
|
|
3265
|
+
const populateHint = auditInstalled ? `, or ask the ${c.bold("audit-harness-fit")} skill to fill it from repository evidence` : "";
|
|
3196
3266
|
log(
|
|
3197
3267
|
infoRow(
|
|
3198
3268
|
"FILL",
|
|
3199
|
-
`${scaffoldFiles.map((f) => c.bold(f)).join(" \xB7 ")} \u2014 a fill-in scaffold. Open and paste each ${c.bold("<!-- FILL: \u2026 -->")} prompt to your agent to tailor it to this project`
|
|
3269
|
+
`${scaffoldFiles.map((f) => c.bold(f)).join(" \xB7 ")} \u2014 a fill-in scaffold. Open and paste each ${c.bold("<!-- FILL: \u2026 -->")} prompt to your agent to tailor it to this project${populateHint}`
|
|
3200
3270
|
)
|
|
3201
3271
|
);
|
|
3202
3272
|
}
|
|
3203
3273
|
log("");
|
|
3204
3274
|
}
|
|
3275
|
+
function countSkillDirs(skillFiles) {
|
|
3276
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3277
|
+
for (const f of skillFiles) {
|
|
3278
|
+
const m = /[\\/]\.agents[\\/]skills[\\/]([^\\/]+)[\\/]/.exec(f);
|
|
3279
|
+
if (m?.[1] !== void 0) ids.add(m[1]);
|
|
3280
|
+
}
|
|
3281
|
+
return ids.size;
|
|
3282
|
+
}
|
|
3205
3283
|
function scaffoldFilesForCli(cli2) {
|
|
3206
3284
|
const files = [];
|
|
3207
3285
|
if (cli2.includes("claude")) {
|
|
@@ -3268,6 +3346,15 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3268
3346
|
if (baseline.updateMode.rootImportAdded) {
|
|
3269
3347
|
log(assetRow("success", "CLAUDE.md", `${HARNESS_IMPORT_LINE} import added`));
|
|
3270
3348
|
}
|
|
3349
|
+
if (baseline.updateMode.rootBlockRefreshed) {
|
|
3350
|
+
log(
|
|
3351
|
+
assetRow(
|
|
3352
|
+
"success",
|
|
3353
|
+
"CLAUDE.md",
|
|
3354
|
+
"harness block refreshed (skills that apply continuously)"
|
|
3355
|
+
)
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3271
3358
|
if (baseline.updateMode.legacyAnchor) {
|
|
3272
3359
|
log(
|
|
3273
3360
|
assetRow(
|
|
@@ -3313,6 +3400,15 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3313
3400
|
)
|
|
3314
3401
|
);
|
|
3315
3402
|
}
|
|
3403
|
+
for (const id of baseline.updateMode.retiredAgents) {
|
|
3404
|
+
log(
|
|
3405
|
+
assetRow(
|
|
3406
|
+
"skip",
|
|
3407
|
+
"agents",
|
|
3408
|
+
`${id} \xB7 \uC774 \uB9B4\uB9AC\uC988\uC5D0\uC11C \uC740\uD1F4 \u2014 .claude/agents/${id}.md \uB97C \uC9C0\uC6CC\uB3C4 \uB41C\uB2E4 \xB7 Claude Code \uC758 \`/code-review\` \xB7 \`/security-review\` \uAC00 \uAC19\uC740 \uC77C\uC744 \uD55C\uB2E4`
|
|
3409
|
+
)
|
|
3410
|
+
);
|
|
3411
|
+
}
|
|
3316
3412
|
if (baseline.updateMode.policyBackedUp.length > 0) {
|
|
3317
3413
|
log(
|
|
3318
3414
|
assetRow(
|
|
@@ -3366,13 +3462,37 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3366
3462
|
log(assetRow("skip", "external skills", `${meta} \xB7 \uB2E4\uC74C update \uC5D0\uC11C \uB2E4\uC2DC \uC2DC\uB3C4\uD55C\uB2E4`));
|
|
3367
3463
|
}
|
|
3368
3464
|
if (baseline.updateMode.externalSkillsNotInCatalog.length > 0) {
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3465
|
+
const ids = baseline.updateMode.externalSkillsNotInCatalog;
|
|
3466
|
+
for (const id of ids) {
|
|
3467
|
+
const renamedTo = RENAMED_SKILL_IDS[id];
|
|
3468
|
+
if (renamedTo) {
|
|
3469
|
+
log(
|
|
3470
|
+
assetRow(
|
|
3471
|
+
"skip",
|
|
3472
|
+
"skills",
|
|
3473
|
+
`${id} \uB294 ${renamedTo} \uAC00 \uB410\uB2E4 \xB7 update \uB4A4 .claude/skills/${id} \uB97C \uC9C0\uC6B0\uACE0 ${renamedTo} \uB97C \uC120\uD0DD\uD574 \uBC1B\uB294\uB2E4`
|
|
3474
|
+
)
|
|
3475
|
+
);
|
|
3476
|
+
} else if (RETIRED_SKILL_IDS.includes(id)) {
|
|
3477
|
+
log(
|
|
3478
|
+
assetRow(
|
|
3479
|
+
"skip",
|
|
3480
|
+
"skills",
|
|
3481
|
+
`${id} \xB7 \uC774 \uB9B4\uB9AC\uC988\uC5D0\uC11C \uC740\uD1F4 \u2014 .claude/skills/${id} \uB97C \uC9C0\uC6CC\uB3C4 \uB41C\uB2E4`
|
|
3482
|
+
)
|
|
3483
|
+
);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
const unknown = ids.filter((id) => !RENAMED_SKILL_IDS[id] && !RETIRED_SKILL_IDS.includes(id));
|
|
3487
|
+
if (unknown.length > 0) {
|
|
3488
|
+
log(
|
|
3489
|
+
assetRow(
|
|
3490
|
+
"skip",
|
|
3491
|
+
"external skills",
|
|
3492
|
+
`${unknown.join(", ")} \xB7 \uCE74\uD0C8\uB85C\uADF8\uC5D0 \uC5C6\uC5B4 \uAC31\uC2E0 \uB300\uC0C1\uC774 \uC544\uB2C8\uB2E4`
|
|
3493
|
+
)
|
|
3494
|
+
);
|
|
3495
|
+
}
|
|
3376
3496
|
}
|
|
3377
3497
|
if (baseline.updateMode.externalSkillsUnknown) {
|
|
3378
3498
|
log(
|
|
@@ -3417,12 +3537,7 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3417
3537
|
);
|
|
3418
3538
|
}
|
|
3419
3539
|
if (cats.hooks.length > 0) {
|
|
3420
|
-
phase1Row(
|
|
3421
|
-
"hooks",
|
|
3422
|
-
cats.hooks.length,
|
|
3423
|
-
"session-start \xB7 protect-files \xB7 task-brief-nudge",
|
|
3424
|
-
cats.hooks
|
|
3425
|
-
);
|
|
3540
|
+
phase1Row("hooks", cats.hooks.length, "session-start \xB7 protect-files", cats.hooks);
|
|
3426
3541
|
}
|
|
3427
3542
|
if (cats.commands > 0) {
|
|
3428
3543
|
phase1Row("commands", cats.commands, "slash commands");
|
|
@@ -3431,7 +3546,7 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3431
3546
|
phase1Row(
|
|
3432
3547
|
"skills",
|
|
3433
3548
|
cats.skills.length,
|
|
3434
|
-
"
|
|
3549
|
+
"deep-research \xB7 ui-visual-review \xB7 eval-harness (modified)",
|
|
3435
3550
|
cats.skills
|
|
3436
3551
|
);
|
|
3437
3552
|
}
|
|
@@ -3759,8 +3874,8 @@ function registerInstallCommand(cli2) {
|
|
|
3759
3874
|
|
|
3760
3875
|
// src/commands/list.ts
|
|
3761
3876
|
init_esm_shims();
|
|
3762
|
-
import { existsSync as
|
|
3763
|
-
import { join as
|
|
3877
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
3878
|
+
import { join as join14, resolve as resolve3 } from "path";
|
|
3764
3879
|
function listAction(options = {}, deps = {}) {
|
|
3765
3880
|
const log = deps.log ?? console.log;
|
|
3766
3881
|
const err = deps.err ?? console.error;
|
|
@@ -3823,8 +3938,8 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3823
3938
|
const rows = [` ${c.dim(dirs.join(" "))}`];
|
|
3824
3939
|
const rootMd = log.templates.rootClaudeMd;
|
|
3825
3940
|
if (rootMd) {
|
|
3826
|
-
const path =
|
|
3827
|
-
const modified =
|
|
3941
|
+
const path = join14(projectDir, rootMd.path);
|
|
3942
|
+
const modified = existsSync15(path) && hashContent(readFileSync14(path, "utf8")) !== rootMd.sha256;
|
|
3828
3943
|
rows.push(
|
|
3829
3944
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
3830
3945
|
);
|
|
@@ -3832,7 +3947,7 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3832
3947
|
return rows;
|
|
3833
3948
|
}
|
|
3834
3949
|
function formatRootFileRows(rootFiles, projectDir) {
|
|
3835
|
-
const present = rootFiles.filter((f) =>
|
|
3950
|
+
const present = rootFiles.filter((f) => existsSync15(join14(projectDir, f.path)));
|
|
3836
3951
|
const width = Math.max(0, ...present.map((f) => f.path.length));
|
|
3837
3952
|
return present.map(
|
|
3838
3953
|
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(f.change === "created" ? "\uC0DD\uC131" : "\uBCD1\uD569")} ${c.dim(f.notes.join(" / "))}`
|
|
@@ -3850,15 +3965,15 @@ function registerListCommand(cli2) {
|
|
|
3850
3965
|
init_esm_shims();
|
|
3851
3966
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3852
3967
|
import {
|
|
3853
|
-
existsSync as
|
|
3968
|
+
existsSync as existsSync16,
|
|
3854
3969
|
lstatSync as lstatSync2,
|
|
3855
3970
|
readdirSync as readdirSync5,
|
|
3856
|
-
readFileSync as
|
|
3971
|
+
readFileSync as readFileSync15,
|
|
3857
3972
|
rmdirSync,
|
|
3858
3973
|
rmSync,
|
|
3859
3974
|
writeFileSync as writeFileSync7
|
|
3860
3975
|
} from "fs";
|
|
3861
|
-
import { dirname as dirname5, join as
|
|
3976
|
+
import { dirname as dirname5, join as join15, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
|
|
3862
3977
|
|
|
3863
3978
|
// src/uninstall-interactive.ts
|
|
3864
3979
|
init_esm_shims();
|
|
@@ -5098,7 +5213,7 @@ function settleLog(ctx, io) {
|
|
|
5098
5213
|
return true;
|
|
5099
5214
|
}
|
|
5100
5215
|
} else {
|
|
5101
|
-
io.rm(
|
|
5216
|
+
io.rm(join15(projectDir, INSTALL_LOG_DIR));
|
|
5102
5217
|
io.rm(legacyInstallLogPath(projectDir));
|
|
5103
5218
|
io.log(` ${status.success("install log removed")}`);
|
|
5104
5219
|
}
|
|
@@ -5148,7 +5263,7 @@ function advisoryLines(plan, projectDir, rootFiles) {
|
|
|
5148
5263
|
return lines;
|
|
5149
5264
|
}
|
|
5150
5265
|
function rootFileAdvisoryLines(rootFiles, projectDir) {
|
|
5151
|
-
const present = rootFiles.filter((f) =>
|
|
5266
|
+
const present = rootFiles.filter((f) => existsSync16(join15(projectDir, f.path)));
|
|
5152
5267
|
if (present.length === 0) return [];
|
|
5153
5268
|
return [
|
|
5154
5269
|
"",
|
|
@@ -5249,16 +5364,16 @@ function buildGlobalAdvisoryCmd(asset) {
|
|
|
5249
5364
|
}
|
|
5250
5365
|
}
|
|
5251
5366
|
function removeTemplates(log, projectDir, rm) {
|
|
5252
|
-
rm(
|
|
5253
|
-
if (log.templates.codexDir) rm(
|
|
5254
|
-
if (log.templates.opencodeDir) rm(
|
|
5367
|
+
rm(join15(projectDir, log.templates.claudeDir));
|
|
5368
|
+
if (log.templates.codexDir) rm(join15(projectDir, log.templates.codexDir));
|
|
5369
|
+
if (log.templates.opencodeDir) rm(join15(projectDir, log.templates.opencodeDir));
|
|
5255
5370
|
const external = removeExternalFiles(log, projectDir, rm);
|
|
5256
5371
|
const importStripped = stripRootImport(projectDir);
|
|
5257
5372
|
const rootMd = log.templates.rootClaudeMd;
|
|
5258
5373
|
if (rootMd) {
|
|
5259
5374
|
if (rootClaudeMdModified(log, projectDir))
|
|
5260
5375
|
return { rootClaudeMdKept: true, importStripped, external };
|
|
5261
|
-
rm(
|
|
5376
|
+
rm(join15(projectDir, rootMd.path));
|
|
5262
5377
|
}
|
|
5263
5378
|
return { rootClaudeMdKept: false, importStripped, external };
|
|
5264
5379
|
}
|
|
@@ -5266,17 +5381,17 @@ function removeExternalFiles(log, projectDir, rm) {
|
|
|
5266
5381
|
const removed = [];
|
|
5267
5382
|
const kept = [];
|
|
5268
5383
|
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
5269
|
-
const abs =
|
|
5270
|
-
if (!
|
|
5384
|
+
const abs = join15(projectDir, path);
|
|
5385
|
+
if (!existsSync16(abs)) continue;
|
|
5271
5386
|
if (!lstatSync2(abs).isFile()) continue;
|
|
5272
|
-
if (hashContent(
|
|
5387
|
+
if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
|
|
5273
5388
|
kept.push(path);
|
|
5274
5389
|
continue;
|
|
5275
5390
|
}
|
|
5276
5391
|
rm(abs);
|
|
5277
5392
|
removed.push(path);
|
|
5278
5393
|
}
|
|
5279
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(
|
|
5394
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(join15(projectDir, path)));
|
|
5280
5395
|
return { removed, kept };
|
|
5281
5396
|
}
|
|
5282
5397
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
@@ -5298,7 +5413,7 @@ function stripRootImport(projectDir) {
|
|
|
5298
5413
|
const stripped = current === null ? null : stripHarnessImport(current);
|
|
5299
5414
|
if (stripped === null) return false;
|
|
5300
5415
|
try {
|
|
5301
|
-
writeFileSync7(
|
|
5416
|
+
writeFileSync7(join15(projectDir, "CLAUDE.md"), stripped, "utf8");
|
|
5302
5417
|
return true;
|
|
5303
5418
|
} catch {
|
|
5304
5419
|
return false;
|
|
@@ -5308,9 +5423,9 @@ function previewExternalLines(installLog, projectDir) {
|
|
|
5308
5423
|
const lines = [];
|
|
5309
5424
|
let removable = 0;
|
|
5310
5425
|
for (const { path, sha256 } of installLog.externalFiles ?? []) {
|
|
5311
|
-
const abs =
|
|
5312
|
-
if (!
|
|
5313
|
-
if (hashContent(
|
|
5426
|
+
const abs = join15(projectDir, path);
|
|
5427
|
+
if (!existsSync16(abs)) continue;
|
|
5428
|
+
if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
|
|
5314
5429
|
lines.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
|
|
5315
5430
|
continue;
|
|
5316
5431
|
}
|
|
@@ -5336,10 +5451,10 @@ function hasRootImport(projectDir) {
|
|
|
5336
5451
|
return current !== null && stripHarnessImport(current) !== null;
|
|
5337
5452
|
}
|
|
5338
5453
|
function readRootClaudeMd(projectDir) {
|
|
5339
|
-
const path =
|
|
5340
|
-
if (!
|
|
5454
|
+
const path = join15(projectDir, "CLAUDE.md");
|
|
5455
|
+
if (!existsSync16(path)) return null;
|
|
5341
5456
|
try {
|
|
5342
|
-
return
|
|
5457
|
+
return readFileSync15(path, "utf8");
|
|
5343
5458
|
} catch {
|
|
5344
5459
|
return null;
|
|
5345
5460
|
}
|
|
@@ -5347,9 +5462,9 @@ function readRootClaudeMd(projectDir) {
|
|
|
5347
5462
|
function rootClaudeMdModified(log, projectDir) {
|
|
5348
5463
|
const rootMd = log.templates.rootClaudeMd;
|
|
5349
5464
|
if (!rootMd) return false;
|
|
5350
|
-
const path =
|
|
5351
|
-
if (!
|
|
5352
|
-
return hashContent(
|
|
5465
|
+
const path = join15(projectDir, rootMd.path);
|
|
5466
|
+
if (!existsSync16(path)) return false;
|
|
5467
|
+
return hashContent(readFileSync15(path, "utf8")) !== rootMd.sha256;
|
|
5353
5468
|
}
|
|
5354
5469
|
function formatTemplateList(log) {
|
|
5355
5470
|
const items = [log.templates.claudeDir];
|
|
@@ -5361,7 +5476,7 @@ function defaultSpawn2(cmd, args) {
|
|
|
5361
5476
|
return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
|
|
5362
5477
|
}
|
|
5363
5478
|
function defaultRm(path) {
|
|
5364
|
-
if (
|
|
5479
|
+
if (existsSync16(path)) {
|
|
5365
5480
|
rmSync(path, { recursive: true, force: true });
|
|
5366
5481
|
}
|
|
5367
5482
|
}
|
|
@@ -5409,8 +5524,8 @@ import { resolve as resolve5 } from "path";
|
|
|
5409
5524
|
|
|
5410
5525
|
// src/state.ts
|
|
5411
5526
|
init_esm_shims();
|
|
5412
|
-
import { existsSync as
|
|
5413
|
-
import { join as
|
|
5527
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
5528
|
+
import { join as join16 } from "path";
|
|
5414
5529
|
var META_FILE = ".claude/.installed-tracks";
|
|
5415
5530
|
var LEGACY_SIGNATURES = [
|
|
5416
5531
|
{ rule: "htmx.md", track: "ssr-htmx" },
|
|
@@ -5420,11 +5535,11 @@ var LEGACY_SIGNATURES = [
|
|
|
5420
5535
|
{ rule: "cli-development.md", track: "tooling" }
|
|
5421
5536
|
];
|
|
5422
5537
|
function detectInstallState(projectDir) {
|
|
5423
|
-
const claudeDir =
|
|
5424
|
-
const hasClaudeDir =
|
|
5538
|
+
const claudeDir = join16(projectDir, ".claude");
|
|
5539
|
+
const hasClaudeDir = existsSync17(claudeDir);
|
|
5425
5540
|
if (hasClaudeDir) {
|
|
5426
|
-
const metaPath =
|
|
5427
|
-
if (
|
|
5541
|
+
const metaPath = join16(projectDir, META_FILE);
|
|
5542
|
+
if (existsSync17(metaPath)) {
|
|
5428
5543
|
const tracks2 = readMetafile(metaPath);
|
|
5429
5544
|
return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
|
|
5430
5545
|
}
|
|
@@ -5446,7 +5561,7 @@ function tracksFromLog(tracks) {
|
|
|
5446
5561
|
return [...new Set(tracks.filter(isTrack))].sort();
|
|
5447
5562
|
}
|
|
5448
5563
|
function readMetafile(path) {
|
|
5449
|
-
const raw =
|
|
5564
|
+
const raw = readFileSync16(path, "utf8");
|
|
5450
5565
|
const seen = /* @__PURE__ */ new Set();
|
|
5451
5566
|
for (const line of raw.split(/\s+/)) {
|
|
5452
5567
|
const trimmed = line.trim();
|
|
@@ -5457,13 +5572,13 @@ function readMetafile(path) {
|
|
|
5457
5572
|
return [...seen].sort();
|
|
5458
5573
|
}
|
|
5459
5574
|
function inferFromLegacySignatures(projectDir) {
|
|
5460
|
-
const rulesDir =
|
|
5461
|
-
if (!
|
|
5575
|
+
const rulesDir = join16(projectDir, ".claude/rules");
|
|
5576
|
+
if (!existsSync17(rulesDir)) {
|
|
5462
5577
|
return [];
|
|
5463
5578
|
}
|
|
5464
5579
|
const found = /* @__PURE__ */ new Set();
|
|
5465
5580
|
for (const sig of LEGACY_SIGNATURES) {
|
|
5466
|
-
if (
|
|
5581
|
+
if (existsSync17(join16(rulesDir, sig.rule))) {
|
|
5467
5582
|
found.add(sig.track);
|
|
5468
5583
|
}
|
|
5469
5584
|
}
|
|
@@ -5593,7 +5708,7 @@ var INSTALL_TARGET_PAGES = [
|
|
|
5593
5708
|
// 2026-08-16 — 트랙이 고르는 자산을 **맨 앞에서** 전부 보여준다. 그전까지 이 항목들은 화면에
|
|
5594
5709
|
// 한 번도 안 나온 채 깔렸고, 사용자는 설치가 끝난 뒤 요약에서 처음 봤다.
|
|
5595
5710
|
//
|
|
5596
|
-
// 2페이지로 나눈 근거는 실측이다: 최대 트랙(`full`)이 32항목(rules 6 · agents 9 · hooks 3 ·
|
|
5711
|
+
// 2페이지로 나눈 근거는 실측이다: 최대 트랙(`full`)이 32항목(당시 실측 — rules 6 · agents 9 · hooks 3 ·
|
|
5597
5712
|
// skills 14)이라 clack groupMultiselect 의 "페이지당 ≤ ~30행" 제약을 한 페이지로는 못 지킨다.
|
|
5598
5713
|
// 가르는 축은 역할이다 — 정책·가드가 한쪽, 실행 능력이 다른 쪽.
|
|
5599
5714
|
{ label: "Track baseline \u2014 Rules & Hooks (\uC815\uCC45\xB7\uAC00\uB4DC)", cats: [], baseline: ["rules", "hooks"] },
|