@uzysjung/agent-harness 26.149.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-YSW3OLH4.js → chunk-3QBHZUVB.js} +164 -66
- package/dist/chunk-3QBHZUVB.js.map +1 -0
- package/dist/index.js +397 -293
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +5 -1
- package/dist/trust-tier-drift.js.map +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/hooks/session-start.sh +57 -3
- 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/audit-service-gaps/SKILL.md +6 -7
- package/templates/skills/clear-korean-communication/SKILL.md +8 -13
- package/templates/skills/compaction-handoff/SKILL.md +29 -12
- package/templates/skills/external-model-consult/SKILL.md +13 -24
- package/templates/skills/model-orchestration/SKILL.md +18 -15
- package/templates/skills/natural-korean/SKILL.md +45 -0
- package/templates/skills/north-star/SKILL.md +4 -6
- package/templates/skills/north-star/references/roadmap-method.md +2 -2
- package/templates/skills/{task-brief → objective-brief}/SKILL.md +17 -18
- package/templates/skills/recurrence-prevention/SKILL.md +16 -16
- package/dist/chunk-YSW3OLH4.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,
|
|
@@ -22,6 +26,7 @@ import {
|
|
|
22
26
|
backupDir,
|
|
23
27
|
backupFile,
|
|
24
28
|
backupFileIfChanged,
|
|
29
|
+
buildAssetSpec,
|
|
25
30
|
buildInstallLog,
|
|
26
31
|
buildManifest,
|
|
27
32
|
collectPolicyHashes,
|
|
@@ -55,8 +60,9 @@ import {
|
|
|
55
60
|
stripHarnessImport,
|
|
56
61
|
summarizeContextCost,
|
|
57
62
|
upsertHarnessImport,
|
|
63
|
+
withContinuousSkillsNote,
|
|
58
64
|
writeInstallLog
|
|
59
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-3QBHZUVB.js";
|
|
60
66
|
|
|
61
67
|
// node_modules/sisteransi/src/index.js
|
|
62
68
|
var require_src = __commonJS({
|
|
@@ -741,7 +747,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
741
747
|
// package.json
|
|
742
748
|
var package_default = {
|
|
743
749
|
name: "@uzysjung/agent-harness",
|
|
744
|
-
version: "26.
|
|
750
|
+
version: "26.151.0",
|
|
745
751
|
description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
|
|
746
752
|
type: "module",
|
|
747
753
|
publishConfig: {
|
|
@@ -979,15 +985,15 @@ function padDisplay(s, width) {
|
|
|
979
985
|
// src/installer.ts
|
|
980
986
|
init_esm_shims();
|
|
981
987
|
import {
|
|
982
|
-
chmodSync as
|
|
983
|
-
existsSync as
|
|
988
|
+
chmodSync as chmodSync3,
|
|
989
|
+
existsSync as existsSync14,
|
|
984
990
|
mkdirSync as mkdirSync4,
|
|
985
991
|
readdirSync as readdirSync4,
|
|
986
|
-
readFileSync as
|
|
992
|
+
readFileSync as readFileSync13,
|
|
987
993
|
unlinkSync as unlinkSync3,
|
|
988
994
|
writeFileSync as writeFileSync6
|
|
989
995
|
} from "fs";
|
|
990
|
-
import { basename as basename5, dirname as dirname4, join as
|
|
996
|
+
import { basename as basename5, dirname as dirname4, join as join13, resolve } from "path";
|
|
991
997
|
|
|
992
998
|
// src/ci-scaffold.ts
|
|
993
999
|
init_esm_shims();
|
|
@@ -1032,8 +1038,8 @@ init_esm_shims();
|
|
|
1032
1038
|
|
|
1033
1039
|
// src/antigravity/transform.ts
|
|
1034
1040
|
init_esm_shims();
|
|
1035
|
-
import { existsSync as
|
|
1036
|
-
import { basename, join as
|
|
1041
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1042
|
+
import { basename, join as join5 } from "path";
|
|
1037
1043
|
|
|
1038
1044
|
// src/codex/agents-md.ts
|
|
1039
1045
|
init_esm_shims();
|
|
@@ -1048,6 +1054,8 @@ function renderAgentsMd(params) {
|
|
|
1048
1054
|
|
|
1049
1055
|
// src/codex/skills.ts
|
|
1050
1056
|
init_esm_shims();
|
|
1057
|
+
import { chmodSync, existsSync as existsSync2, readFileSync } from "fs";
|
|
1058
|
+
import { join as join2 } from "path";
|
|
1051
1059
|
function renderBundledSkill(source) {
|
|
1052
1060
|
const trimmed = source.trimEnd();
|
|
1053
1061
|
const lines = trimmed.split(/\r?\n/);
|
|
@@ -1075,16 +1083,46 @@ ${portBody(body)}
|
|
|
1075
1083
|
function portBody(body) {
|
|
1076
1084
|
return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
|
|
1077
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
|
+
}
|
|
1078
1116
|
|
|
1079
1117
|
// src/owned-write.ts
|
|
1080
1118
|
init_esm_shims();
|
|
1081
|
-
import { existsSync as
|
|
1119
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1082
1120
|
import { dirname, relative, sep } from "path";
|
|
1083
1121
|
|
|
1084
1122
|
// src/foreign-slot.ts
|
|
1085
1123
|
init_esm_shims();
|
|
1086
1124
|
import { lstatSync } from "fs";
|
|
1087
|
-
import { join as
|
|
1125
|
+
import { join as join3 } from "path";
|
|
1088
1126
|
var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
|
|
1089
1127
|
function occupiedByNonDirectory(path) {
|
|
1090
1128
|
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
@@ -1093,15 +1131,15 @@ function occupiedByNonDirectory(path) {
|
|
|
1093
1131
|
function foreignOwnedTarget(projectDir, rel) {
|
|
1094
1132
|
const slot = SKILL_SLOT.exec(rel)?.[1];
|
|
1095
1133
|
if (slot === void 0) return null;
|
|
1096
|
-
if (occupiedByNonDirectory(
|
|
1134
|
+
if (occupiedByNonDirectory(join3(projectDir, slot))) return slot;
|
|
1097
1135
|
if (rel !== slot) {
|
|
1098
1136
|
const parts = rel.slice(slot.length + 1).split("/");
|
|
1099
1137
|
let cursor = slot;
|
|
1100
1138
|
for (const part of parts.slice(0, -1)) {
|
|
1101
1139
|
cursor = `${cursor}/${part}`;
|
|
1102
|
-
if (occupiedByNonDirectory(
|
|
1140
|
+
if (occupiedByNonDirectory(join3(projectDir, cursor))) return cursor;
|
|
1103
1141
|
}
|
|
1104
|
-
const leaf = lstatSync(
|
|
1142
|
+
const leaf = lstatSync(join3(projectDir, rel), { throwIfNoEntry: false });
|
|
1105
1143
|
if (leaf !== void 0 && !leaf.isFile()) return rel;
|
|
1106
1144
|
}
|
|
1107
1145
|
return null;
|
|
@@ -1124,13 +1162,13 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1124
1162
|
return false;
|
|
1125
1163
|
}
|
|
1126
1164
|
const digest = hashContent(content);
|
|
1127
|
-
if (!
|
|
1165
|
+
if (!existsSync3(absPath)) {
|
|
1128
1166
|
if (refreshOnly && !opts?.createInRefresh) return false;
|
|
1129
1167
|
mkdirSync(dirname(absPath), { recursive: true });
|
|
1130
1168
|
writeFileSync(absPath, content);
|
|
1131
1169
|
updated++;
|
|
1132
1170
|
} else {
|
|
1133
|
-
const current =
|
|
1171
|
+
const current = readFileSync2(absPath, "utf8");
|
|
1134
1172
|
if (current !== content) {
|
|
1135
1173
|
if (!isHarnessOwned(baseline, rel, current)) {
|
|
1136
1174
|
backupPaths.push(backupFile(absPath, now));
|
|
@@ -1157,8 +1195,8 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
|
|
|
1157
1195
|
|
|
1158
1196
|
// src/rules-port.ts
|
|
1159
1197
|
init_esm_shims();
|
|
1160
|
-
import { existsSync as
|
|
1161
|
-
import { join as
|
|
1198
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1199
|
+
import { join as join4 } from "path";
|
|
1162
1200
|
function stripClaudeFrontmatter(source) {
|
|
1163
1201
|
if (!source.startsWith("---\n")) return source.trim();
|
|
1164
1202
|
const end = source.indexOf("\n---", 4);
|
|
@@ -1168,9 +1206,9 @@ function stripClaudeFrontmatter(source) {
|
|
|
1168
1206
|
function portRules(harnessRoot, ruleNames) {
|
|
1169
1207
|
const ported = [];
|
|
1170
1208
|
for (const name of ruleNames) {
|
|
1171
|
-
const src =
|
|
1172
|
-
if (!
|
|
1173
|
-
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")) });
|
|
1174
1212
|
}
|
|
1175
1213
|
return ported;
|
|
1176
1214
|
}
|
|
@@ -1190,27 +1228,23 @@ function runAntigravityTransform(params) {
|
|
|
1190
1228
|
refreshOnly
|
|
1191
1229
|
} = params;
|
|
1192
1230
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1193
|
-
const rulesFile = writeRules(harnessRoot, projectDir, writer);
|
|
1231
|
+
const rulesFile = writeRules(harnessRoot, projectDir, writer, selectedInternalSkills);
|
|
1194
1232
|
const antigravityInstalled = rulesFile !== null;
|
|
1195
1233
|
const harnessRuleFiles = [];
|
|
1196
1234
|
for (const rule of portRules(harnessRoot, rules)) {
|
|
1197
|
-
const target =
|
|
1235
|
+
const target = join5(projectDir, ".agents", "rules", `${rule.name}.md`);
|
|
1198
1236
|
if (!writer.write(target, `${rule.body}
|
|
1199
1237
|
`, { createInRefresh: antigravityInstalled })) {
|
|
1200
1238
|
continue;
|
|
1201
1239
|
}
|
|
1202
1240
|
harnessRuleFiles.push(target);
|
|
1203
1241
|
}
|
|
1204
|
-
const skillFiles =
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
const target = join4(projectDir, ".agents", "skills", id, "SKILL.md");
|
|
1211
|
-
if (!writer.write(target, renderBundledSkill(readFileSync3(src, "utf8")))) continue;
|
|
1212
|
-
skillFiles.push(target);
|
|
1213
|
-
}
|
|
1242
|
+
const skillFiles = writeBundledSkillDirs({
|
|
1243
|
+
harnessRoot,
|
|
1244
|
+
projectDir,
|
|
1245
|
+
skillIds: selectedInternalSkills,
|
|
1246
|
+
writer
|
|
1247
|
+
});
|
|
1214
1248
|
return {
|
|
1215
1249
|
rulesFile,
|
|
1216
1250
|
harnessRuleFiles,
|
|
@@ -1218,20 +1252,24 @@ function runAntigravityTransform(params) {
|
|
|
1218
1252
|
ownership: writer.result()
|
|
1219
1253
|
};
|
|
1220
1254
|
}
|
|
1221
|
-
function writeRules(harnessRoot, projectDir, writer) {
|
|
1222
|
-
const claudeMdPath =
|
|
1223
|
-
const templatePath =
|
|
1224
|
-
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)) {
|
|
1225
1259
|
return null;
|
|
1226
1260
|
}
|
|
1227
|
-
const claudeMd =
|
|
1228
|
-
const template =
|
|
1229
|
-
const target =
|
|
1261
|
+
const claudeMd = readFileSync4(claudeMdPath, "utf8");
|
|
1262
|
+
const template = readFileSync4(templatePath, "utf8");
|
|
1263
|
+
const target = join5(projectDir, ".agents", "rules", "uzys-harness.md");
|
|
1230
1264
|
const rulesOut = renderAgentsMd({
|
|
1231
1265
|
template,
|
|
1232
1266
|
claudeMd,
|
|
1233
1267
|
projectName: basename(projectDir),
|
|
1234
|
-
|
|
1268
|
+
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
1269
|
+
projectContext: withContinuousSkillsNote(
|
|
1270
|
+
renderFillScaffold("antigravity-rule"),
|
|
1271
|
+
selectedInternalSkills
|
|
1272
|
+
)
|
|
1235
1273
|
});
|
|
1236
1274
|
return writer.write(target, rulesOut) ? target : null;
|
|
1237
1275
|
}
|
|
@@ -1239,18 +1277,18 @@ function writeRules(harnessRoot, projectDir, writer) {
|
|
|
1239
1277
|
// src/codex/opt-in.ts
|
|
1240
1278
|
init_esm_shims();
|
|
1241
1279
|
import { homedir } from "os";
|
|
1242
|
-
import { join as
|
|
1280
|
+
import { join as join6 } from "path";
|
|
1243
1281
|
|
|
1244
1282
|
// src/codex/trust-entry.ts
|
|
1245
1283
|
init_esm_shims();
|
|
1246
|
-
import { existsSync as
|
|
1284
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
1247
1285
|
import { dirname as dirname2 } from "path";
|
|
1248
1286
|
var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
|
|
1249
1287
|
function registerTrustEntry(opts) {
|
|
1250
1288
|
const { configPath, projectDir } = opts;
|
|
1251
1289
|
try {
|
|
1252
1290
|
mkdirSync2(dirname2(configPath), { recursive: true });
|
|
1253
|
-
const existing =
|
|
1291
|
+
const existing = existsSync6(configPath) ? readFileSync5(configPath, "utf8") : "";
|
|
1254
1292
|
if (hasTrustEntry(existing, projectDir)) {
|
|
1255
1293
|
return { status: "already-present" };
|
|
1256
1294
|
}
|
|
@@ -1274,8 +1312,8 @@ function hasTrustEntry(configContent, projectDir) {
|
|
|
1274
1312
|
|
|
1275
1313
|
// src/codex/opt-in.ts
|
|
1276
1314
|
function runCodexOptIn(ctx) {
|
|
1277
|
-
const codexHome = ctx.codexHome ??
|
|
1278
|
-
const configPath =
|
|
1315
|
+
const codexHome = ctx.codexHome ?? join6(homedir(), ".codex");
|
|
1316
|
+
const configPath = join6(codexHome, "config.toml");
|
|
1279
1317
|
const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
|
|
1280
1318
|
return {
|
|
1281
1319
|
trustEntry: {
|
|
@@ -1288,8 +1326,8 @@ function runCodexOptIn(ctx) {
|
|
|
1288
1326
|
|
|
1289
1327
|
// src/codex/transform.ts
|
|
1290
1328
|
init_esm_shims();
|
|
1291
|
-
import { chmodSync, existsSync as
|
|
1292
|
-
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";
|
|
1293
1331
|
|
|
1294
1332
|
// src/codex/config-toml.ts
|
|
1295
1333
|
init_esm_shims();
|
|
@@ -1366,23 +1404,27 @@ function runCodexTransform(params) {
|
|
|
1366
1404
|
refreshOnly
|
|
1367
1405
|
} = params;
|
|
1368
1406
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1369
|
-
const claudeMd = readRequired(
|
|
1370
|
-
const agentsTemplate = readRequired(
|
|
1371
|
-
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"));
|
|
1372
1410
|
const projectName = basename2(projectDir);
|
|
1373
|
-
const mcp = readOptionalJson(
|
|
1374
|
-
const agentsMdPath =
|
|
1411
|
+
const mcp = readOptionalJson(join7(harnessRoot, ".mcp.json"));
|
|
1412
|
+
const agentsMdPath = join7(projectDir, "AGENTS.md");
|
|
1375
1413
|
ensureDir(projectDir);
|
|
1376
1414
|
const agentsMdOut = renderAgentsMd({
|
|
1377
1415
|
template: agentsTemplate,
|
|
1378
1416
|
claudeMd,
|
|
1379
1417
|
projectName,
|
|
1380
|
-
|
|
1418
|
+
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
1419
|
+
projectContext: withContinuousSkillsNote(
|
|
1420
|
+
renderFillScaffold("agents-md"),
|
|
1421
|
+
selectedInternalSkills
|
|
1422
|
+
),
|
|
1381
1423
|
// Codex 는 룰 디렉터리가 없다 — 룰이 AGENTS.md 본문에 들어가야 도달한다(§Harness Rules).
|
|
1382
1424
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1383
1425
|
});
|
|
1384
1426
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1385
|
-
const configTomlPath =
|
|
1427
|
+
const configTomlPath = join7(projectDir, ".codex/config.toml");
|
|
1386
1428
|
writer.write(
|
|
1387
1429
|
configTomlPath,
|
|
1388
1430
|
renderConfigToml({
|
|
@@ -1392,29 +1434,25 @@ function runCodexTransform(params) {
|
|
|
1392
1434
|
mcp
|
|
1393
1435
|
})
|
|
1394
1436
|
);
|
|
1395
|
-
const hookDir =
|
|
1437
|
+
const hookDir = join7(projectDir, ".codex/hooks");
|
|
1396
1438
|
const hookFiles = [];
|
|
1397
1439
|
for (const hook of HOOK_NAMES) {
|
|
1398
|
-
const src =
|
|
1399
|
-
if (!
|
|
1440
|
+
const src = join7(harnessRoot, "templates/hooks", `${hook}.sh`);
|
|
1441
|
+
if (!existsSync7(src)) {
|
|
1400
1442
|
continue;
|
|
1401
1443
|
}
|
|
1402
|
-
const ported =
|
|
1403
|
-
const target =
|
|
1444
|
+
const ported = readFileSync6(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
|
|
1445
|
+
const target = join7(hookDir, `${hook}.sh`);
|
|
1404
1446
|
if (!writer.write(target, ported)) continue;
|
|
1405
|
-
|
|
1447
|
+
chmodSync2(target, 493);
|
|
1406
1448
|
hookFiles.push(target);
|
|
1407
1449
|
}
|
|
1408
|
-
const skillFiles =
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
const target = join6(projectDir, ".agents", "skills", id, "SKILL.md");
|
|
1415
|
-
if (!writer.write(target, renderBundledSkill(readFileSync5(src, "utf8")))) continue;
|
|
1416
|
-
skillFiles.push(target);
|
|
1417
|
-
}
|
|
1450
|
+
const skillFiles = writeBundledSkillDirs({
|
|
1451
|
+
harnessRoot,
|
|
1452
|
+
projectDir,
|
|
1453
|
+
skillIds: selectedInternalSkills,
|
|
1454
|
+
writer
|
|
1455
|
+
});
|
|
1418
1456
|
return {
|
|
1419
1457
|
agentsMdPath,
|
|
1420
1458
|
configTomlPath,
|
|
@@ -1424,17 +1462,17 @@ function runCodexTransform(params) {
|
|
|
1424
1462
|
};
|
|
1425
1463
|
}
|
|
1426
1464
|
function readRequired(path) {
|
|
1427
|
-
if (!
|
|
1465
|
+
if (!existsSync7(path)) {
|
|
1428
1466
|
throw new Error(`Codex transform: required source missing: ${path}`);
|
|
1429
1467
|
}
|
|
1430
|
-
return
|
|
1468
|
+
return readFileSync6(path, "utf8");
|
|
1431
1469
|
}
|
|
1432
1470
|
function readOptionalJson(path) {
|
|
1433
|
-
if (!
|
|
1471
|
+
if (!existsSync7(path)) {
|
|
1434
1472
|
return null;
|
|
1435
1473
|
}
|
|
1436
1474
|
try {
|
|
1437
|
-
return JSON.parse(
|
|
1475
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
1438
1476
|
} catch {
|
|
1439
1477
|
return null;
|
|
1440
1478
|
}
|
|
@@ -1442,8 +1480,8 @@ function readOptionalJson(path) {
|
|
|
1442
1480
|
|
|
1443
1481
|
// src/opencode/transform.ts
|
|
1444
1482
|
init_esm_shims();
|
|
1445
|
-
import { existsSync as
|
|
1446
|
-
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";
|
|
1447
1485
|
|
|
1448
1486
|
// src/opencode/agents-md.ts
|
|
1449
1487
|
init_esm_shims();
|
|
@@ -1486,46 +1524,46 @@ function runOpencodeTransform(params) {
|
|
|
1486
1524
|
refreshOnly
|
|
1487
1525
|
} = params;
|
|
1488
1526
|
const writer = createOwnedWriter(projectDir, baseline, { refreshOnly: refreshOnly ?? false });
|
|
1489
|
-
const claudeMd = readRequired2(
|
|
1490
|
-
const agentsTemplate = readRequired2(
|
|
1527
|
+
const claudeMd = readRequired2(join8(harnessRoot, "templates/CLAUDE.md"));
|
|
1528
|
+
const agentsTemplate = readRequired2(join8(harnessRoot, "templates/opencode/AGENTS.md.template"));
|
|
1491
1529
|
const opencodeTemplate = readRequired2(
|
|
1492
|
-
|
|
1530
|
+
join8(harnessRoot, "templates/opencode/opencode.json.template")
|
|
1493
1531
|
);
|
|
1494
1532
|
const projectName = basename3(projectDir);
|
|
1495
|
-
const mcp = readOptionalJson2(
|
|
1533
|
+
const mcp = readOptionalJson2(join8(harnessRoot, ".mcp.json"));
|
|
1496
1534
|
ensureDir(projectDir);
|
|
1497
|
-
const agentsMdPath =
|
|
1535
|
+
const agentsMdPath = join8(projectDir, "AGENTS.md");
|
|
1498
1536
|
const agentsMdOut = renderAgentsMd2({
|
|
1499
1537
|
template: agentsTemplate,
|
|
1500
1538
|
claudeMd,
|
|
1501
1539
|
projectName,
|
|
1502
|
-
|
|
1540
|
+
// ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
|
|
1541
|
+
projectContext: withContinuousSkillsNote(
|
|
1542
|
+
renderFillScaffold("agents-md"),
|
|
1543
|
+
selectedInternalSkills
|
|
1544
|
+
),
|
|
1503
1545
|
// codex 와 **같은 파일**(프로젝트 루트 `AGENTS.md`)이다. 두 transform 이 서로 다른 본문을
|
|
1504
1546
|
// 쓰면 나중에 도는 쪽이 앞선 쪽을 덮어써, codex+opencode 조합에서 룰이 통째로 사라진다
|
|
1505
1547
|
// (독립 검증 C-1 실측). 같은 내용을 쓰면 순서가 결과를 바꾸지 않는다.
|
|
1506
1548
|
harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
|
|
1507
1549
|
});
|
|
1508
1550
|
writer.write(agentsMdPath, agentsMdOut);
|
|
1509
|
-
const opencodeJsonPath =
|
|
1551
|
+
const opencodeJsonPath = join8(projectDir, "opencode.json");
|
|
1510
1552
|
writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
|
|
1511
|
-
const skillFiles =
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
const target = join7(projectDir, ".agents", "skills", id, "SKILL.md");
|
|
1518
|
-
if (!writer.write(target, renderBundledSkill(readFileSync6(src, "utf8")))) continue;
|
|
1519
|
-
skillFiles.push(target);
|
|
1520
|
-
}
|
|
1553
|
+
const skillFiles = writeBundledSkillDirs({
|
|
1554
|
+
harnessRoot,
|
|
1555
|
+
projectDir,
|
|
1556
|
+
skillIds: selectedInternalSkills,
|
|
1557
|
+
writer
|
|
1558
|
+
});
|
|
1521
1559
|
const retiredCommands = [];
|
|
1522
|
-
const cmdDir =
|
|
1523
|
-
if (
|
|
1560
|
+
const cmdDir = join8(projectDir, ".opencode/commands");
|
|
1561
|
+
if (existsSync8(cmdDir)) {
|
|
1524
1562
|
for (const entry of readdirSync(cmdDir)) {
|
|
1525
1563
|
if (!entry.endsWith(".md")) continue;
|
|
1526
1564
|
const id = entry.slice(0, -3);
|
|
1527
|
-
if (!
|
|
1528
|
-
const victim =
|
|
1565
|
+
if (!existsSync8(join8(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
|
|
1566
|
+
const victim = join8(cmdDir, entry);
|
|
1529
1567
|
try {
|
|
1530
1568
|
backupFile(victim);
|
|
1531
1569
|
unlinkSync(victim);
|
|
@@ -1543,17 +1581,17 @@ function runOpencodeTransform(params) {
|
|
|
1543
1581
|
};
|
|
1544
1582
|
}
|
|
1545
1583
|
function readRequired2(path) {
|
|
1546
|
-
if (!
|
|
1584
|
+
if (!existsSync8(path)) {
|
|
1547
1585
|
throw new Error(`OpenCode transform: required source missing: ${path}`);
|
|
1548
1586
|
}
|
|
1549
|
-
return
|
|
1587
|
+
return readFileSync7(path, "utf8");
|
|
1550
1588
|
}
|
|
1551
1589
|
function readOptionalJson2(path) {
|
|
1552
|
-
if (!
|
|
1590
|
+
if (!existsSync8(path)) {
|
|
1553
1591
|
return null;
|
|
1554
1592
|
}
|
|
1555
1593
|
try {
|
|
1556
|
-
return JSON.parse(
|
|
1594
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1557
1595
|
} catch {
|
|
1558
1596
|
return null;
|
|
1559
1597
|
}
|
|
@@ -1645,8 +1683,8 @@ function runCliTransforms(params) {
|
|
|
1645
1683
|
|
|
1646
1684
|
// src/env-files.ts
|
|
1647
1685
|
init_esm_shims();
|
|
1648
|
-
import { appendFileSync, existsSync as
|
|
1649
|
-
import { join as
|
|
1686
|
+
import { appendFileSync, existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
1687
|
+
import { join as join9 } from "path";
|
|
1650
1688
|
var ENV_EXAMPLE_BODY = `# .env.example \u2014 csr-supabase Track
|
|
1651
1689
|
# Copy to .env (gitignored) and fill in values: cp .env.example .env
|
|
1652
1690
|
|
|
@@ -1683,19 +1721,19 @@ function writeEnvExample(projectDir, tracks) {
|
|
|
1683
1721
|
if (!tracks.some((t) => ENV_EXAMPLE_TRACKS.includes(t))) {
|
|
1684
1722
|
return false;
|
|
1685
1723
|
}
|
|
1686
|
-
const path =
|
|
1687
|
-
if (
|
|
1724
|
+
const path = join9(projectDir, ".env.example");
|
|
1725
|
+
if (existsSync9(path)) {
|
|
1688
1726
|
return false;
|
|
1689
1727
|
}
|
|
1690
1728
|
writeFileSync3(path, ENV_EXAMPLE_BODY);
|
|
1691
1729
|
return true;
|
|
1692
1730
|
}
|
|
1693
1731
|
function addGitignoreEnv(projectDir) {
|
|
1694
|
-
const path =
|
|
1695
|
-
if (!
|
|
1732
|
+
const path = join9(projectDir, ".gitignore");
|
|
1733
|
+
if (!existsSync9(path)) {
|
|
1696
1734
|
return false;
|
|
1697
1735
|
}
|
|
1698
|
-
const content =
|
|
1736
|
+
const content = readFileSync8(path, "utf8");
|
|
1699
1737
|
if (GITIGNORE_ENV_PATTERN.test(content)) {
|
|
1700
1738
|
return false;
|
|
1701
1739
|
}
|
|
@@ -1709,11 +1747,11 @@ function addGitignoreEnv(projectDir) {
|
|
|
1709
1747
|
var AGENT_ARTIFACT_DIRS = [".factory/", ".goose/", ".uzys-agent-harness/"];
|
|
1710
1748
|
var GITIGNORE_AGENT_ARTIFACT_HEADER = "# agent CLI / harness \uC790\uB3D9 \uC0DD\uC131\uBB3C (auto-added by agent-harness)";
|
|
1711
1749
|
function addGitignoreAgentArtifacts(projectDir) {
|
|
1712
|
-
const path =
|
|
1713
|
-
if (!
|
|
1750
|
+
const path = join9(projectDir, ".gitignore");
|
|
1751
|
+
if (!existsSync9(path)) {
|
|
1714
1752
|
return [];
|
|
1715
1753
|
}
|
|
1716
|
-
const content =
|
|
1754
|
+
const content = readFileSync8(path, "utf8");
|
|
1717
1755
|
const missing = AGENT_ARTIFACT_DIRS.filter((pattern) => {
|
|
1718
1756
|
const lineRegex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\//g, "/")}\\s*$`, "m");
|
|
1719
1757
|
return !lineRegex.test(content);
|
|
@@ -1732,9 +1770,9 @@ ${block}
|
|
|
1732
1770
|
// src/external-installer.ts
|
|
1733
1771
|
init_esm_shims();
|
|
1734
1772
|
import { spawnSync } from "child_process";
|
|
1735
|
-
import { existsSync as
|
|
1773
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
|
|
1736
1774
|
import { homedir as homedir2 } from "os";
|
|
1737
|
-
import { join as
|
|
1775
|
+
import { join as join10 } from "path";
|
|
1738
1776
|
var DEFAULT_SPAWN_TIMEOUT_MS = 12e4;
|
|
1739
1777
|
function selectExternalTargets(assets, ctx) {
|
|
1740
1778
|
const conditionPassed = filterApplicableAssets(assets, ctx).filter(
|
|
@@ -1810,8 +1848,8 @@ function installOne(asset, ctx) {
|
|
|
1810
1848
|
cwd
|
|
1811
1849
|
);
|
|
1812
1850
|
case "shell-script": {
|
|
1813
|
-
const scriptPath =
|
|
1814
|
-
if (!
|
|
1851
|
+
const scriptPath = join10(ctx.harnessRoot, method.script);
|
|
1852
|
+
if (!existsSync10(scriptPath)) {
|
|
1815
1853
|
return {
|
|
1816
1854
|
asset,
|
|
1817
1855
|
ok: false,
|
|
@@ -1941,17 +1979,17 @@ function detectVersion(method, spawn) {
|
|
|
1941
1979
|
if (at2 <= 0) return void 0;
|
|
1942
1980
|
const plugin = method.pluginId.slice(0, at2);
|
|
1943
1981
|
const marketplaceShort = method.pluginId.slice(at2 + 1);
|
|
1944
|
-
const cacheBase =
|
|
1945
|
-
if (!
|
|
1982
|
+
const cacheBase = join10(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
|
|
1983
|
+
if (!existsSync10(cacheBase)) return void 0;
|
|
1946
1984
|
const versions = readdirSync2(cacheBase).filter((v2) => /^\d/.test(v2)).sort();
|
|
1947
1985
|
return versions.at(-1);
|
|
1948
1986
|
}
|
|
1949
1987
|
case "npm": {
|
|
1950
1988
|
const npmRoot = getNpmGlobalRoot(spawn);
|
|
1951
1989
|
if (!npmRoot) return void 0;
|
|
1952
|
-
const pkgJson =
|
|
1953
|
-
if (!
|
|
1954
|
-
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"));
|
|
1955
1993
|
return parsed.version;
|
|
1956
1994
|
}
|
|
1957
1995
|
default:
|
|
@@ -1978,7 +2016,7 @@ function getNpmGlobalRoot(spawn) {
|
|
|
1978
2016
|
|
|
1979
2017
|
// src/mcp-merge.ts
|
|
1980
2018
|
init_esm_shims();
|
|
1981
|
-
import { existsSync as
|
|
2019
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
1982
2020
|
function parseTrackMcpMap(raw) {
|
|
1983
2021
|
const rows = [];
|
|
1984
2022
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -2029,15 +2067,15 @@ function mergeMcpServers(base, rows, tracks) {
|
|
|
2029
2067
|
return out;
|
|
2030
2068
|
}
|
|
2031
2069
|
function composeMcpJson(opts) {
|
|
2032
|
-
const base = JSON.parse(
|
|
2033
|
-
const merged = opts.existingPath &&
|
|
2034
|
-
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") : "";
|
|
2035
2073
|
const rows = parseTrackMcpMap(mapRaw);
|
|
2036
2074
|
return mergeMcpServers(merged, rows, opts.tracks);
|
|
2037
2075
|
}
|
|
2038
2076
|
function mergeUserBase(base, existingPath) {
|
|
2039
2077
|
try {
|
|
2040
|
-
const existing = JSON.parse(
|
|
2078
|
+
const existing = JSON.parse(readFileSync10(existingPath, "utf8"));
|
|
2041
2079
|
return {
|
|
2042
2080
|
...base,
|
|
2043
2081
|
mcpServers: { ...base.mcpServers, ...existing.mcpServers }
|
|
@@ -2053,8 +2091,8 @@ function writeMcpJson(path, mcp) {
|
|
|
2053
2091
|
|
|
2054
2092
|
// src/superseded.ts
|
|
2055
2093
|
init_esm_shims();
|
|
2056
|
-
import { existsSync as
|
|
2057
|
-
import { join as
|
|
2094
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
2095
|
+
import { join as join11 } from "path";
|
|
2058
2096
|
function sameTracks(a, b2) {
|
|
2059
2097
|
if (a.length !== b2.length) return false;
|
|
2060
2098
|
const sa = [...a].sort();
|
|
@@ -2078,9 +2116,9 @@ function findSuperseded(projectDir, spec, previous) {
|
|
|
2078
2116
|
if (!id) continue;
|
|
2079
2117
|
const key = policyKey(entry.target.slice(".claude/".length));
|
|
2080
2118
|
if (!key) continue;
|
|
2081
|
-
const abs =
|
|
2082
|
-
if (!
|
|
2083
|
-
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;
|
|
2084
2122
|
out.push({ target: entry.target, id, source: entry.source });
|
|
2085
2123
|
}
|
|
2086
2124
|
return out;
|
|
@@ -2090,14 +2128,14 @@ function findSuperseded(projectDir, spec, previous) {
|
|
|
2090
2128
|
init_esm_shims();
|
|
2091
2129
|
import {
|
|
2092
2130
|
copyFileSync,
|
|
2093
|
-
existsSync as
|
|
2131
|
+
existsSync as existsSync13,
|
|
2094
2132
|
mkdirSync as mkdirSync3,
|
|
2095
2133
|
readdirSync as readdirSync3,
|
|
2096
|
-
readFileSync as
|
|
2134
|
+
readFileSync as readFileSync12,
|
|
2097
2135
|
unlinkSync as unlinkSync2,
|
|
2098
2136
|
writeFileSync as writeFileSync5
|
|
2099
2137
|
} from "fs";
|
|
2100
|
-
import { basename as basename4, dirname as dirname3, join as
|
|
2138
|
+
import { basename as basename4, dirname as dirname3, join as join12 } from "path";
|
|
2101
2139
|
var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
|
|
2102
2140
|
function buildUpdateSpec(projectDir, tracks) {
|
|
2103
2141
|
return {
|
|
@@ -2109,7 +2147,7 @@ function buildUpdateSpec(projectDir, tracks) {
|
|
|
2109
2147
|
};
|
|
2110
2148
|
}
|
|
2111
2149
|
function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
2112
|
-
const claudeDir =
|
|
2150
|
+
const claudeDir = join12(projectDir, ".claude");
|
|
2113
2151
|
const report = {
|
|
2114
2152
|
updated: {},
|
|
2115
2153
|
pruned: {},
|
|
@@ -2117,6 +2155,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2117
2155
|
claudeMdUpdated: false,
|
|
2118
2156
|
anchorCreated: false,
|
|
2119
2157
|
rootImportAdded: false,
|
|
2158
|
+
rootBlockRefreshed: false,
|
|
2120
2159
|
legacyAnchor: null,
|
|
2121
2160
|
skillsBackedUp: [],
|
|
2122
2161
|
skillsSkippedLinks: [],
|
|
@@ -2131,6 +2170,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2131
2170
|
installedNew: [],
|
|
2132
2171
|
restored: [],
|
|
2133
2172
|
needsReinstall: [],
|
|
2173
|
+
retiredAgents: [],
|
|
2134
2174
|
mcpAllowlistRetired: null
|
|
2135
2175
|
};
|
|
2136
2176
|
const fresh = installNewAssets(projectDir, templatesDir);
|
|
@@ -2139,8 +2179,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2139
2179
|
report.needsReinstall = fresh.needsReinstall;
|
|
2140
2180
|
const policyBase = policyBaseline(projectDir);
|
|
2141
2181
|
for (const { dir, ext } of POLICY_DIRS) {
|
|
2142
|
-
const target =
|
|
2143
|
-
const source =
|
|
2182
|
+
const target = join12(claudeDir, dir);
|
|
2183
|
+
const source = join12(templatesDir, dir);
|
|
2144
2184
|
const label = `.claude/${dir}`;
|
|
2145
2185
|
const ctx = { prefix: dir, baseline: policyBase };
|
|
2146
2186
|
const synced = updateDir(target, source, ext, ctx);
|
|
@@ -2150,8 +2190,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2150
2190
|
}
|
|
2151
2191
|
refreshPolicyBaseline(projectDir, templatesDir);
|
|
2152
2192
|
const skillSync = syncSkills(
|
|
2153
|
-
|
|
2154
|
-
|
|
2193
|
+
join12(claudeDir, "skills"),
|
|
2194
|
+
join12(templatesDir, "skills"),
|
|
2155
2195
|
skillBaseline(projectDir),
|
|
2156
2196
|
/* @__PURE__ */ new Date(),
|
|
2157
2197
|
(relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`)
|
|
@@ -2161,14 +2201,15 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2161
2201
|
report.skillsSkippedLinks = skillSync.skippedLinks;
|
|
2162
2202
|
refreshSkillBaseline(projectDir);
|
|
2163
2203
|
const installLog = readInstallLog(projectDir);
|
|
2164
|
-
if (
|
|
2204
|
+
if (existsSync13(claudeDir) && (installLog === null || installLog.spec.cli.includes("claude"))) {
|
|
2165
2205
|
syncHarnessAnchor(projectDir, templatesDir, report);
|
|
2166
2206
|
}
|
|
2167
|
-
const settingsPath =
|
|
2168
|
-
if (
|
|
2207
|
+
const settingsPath = join12(claudeDir, "settings.json");
|
|
2208
|
+
if (existsSync13(settingsPath)) {
|
|
2169
2209
|
report.staleHookRefs = cleanStaleHookRefs(settingsPath, claudeDir);
|
|
2170
2210
|
}
|
|
2171
2211
|
report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
|
|
2212
|
+
report.retiredAgents = staleAgentFiles(claudeDir);
|
|
2172
2213
|
const external = refreshExternalCli(projectDir, harnessRoot);
|
|
2173
2214
|
report.externalUpdated = external.externalUpdated;
|
|
2174
2215
|
report.externalBackedUp = external.externalBackedUp;
|
|
@@ -2185,10 +2226,24 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
|
|
|
2185
2226
|
const skillRefresh = (deps.refreshSkills ?? refreshExternalSkills)(projectDir);
|
|
2186
2227
|
report.externalSkillsRefreshed = skillRefresh.refreshed;
|
|
2187
2228
|
report.externalSkillsFailed = skillRefresh.failed;
|
|
2188
|
-
report.externalSkillsNotInCatalog =
|
|
2229
|
+
report.externalSkillsNotInCatalog = [
|
|
2230
|
+
...skillRefresh.notInCatalog,
|
|
2231
|
+
...staleSkillDirs(claudeDir).filter((id) => !skillRefresh.notInCatalog.includes(id))
|
|
2232
|
+
];
|
|
2189
2233
|
report.externalSkillsUnknown = skillRefresh.unknown;
|
|
2190
2234
|
return report;
|
|
2191
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
|
+
}
|
|
2192
2247
|
function installNewAssets(projectDir, templatesDir) {
|
|
2193
2248
|
const installed = [];
|
|
2194
2249
|
const restored = [];
|
|
@@ -2201,15 +2256,15 @@ function installNewAssets(projectDir, templatesDir) {
|
|
|
2201
2256
|
for (const entry of trackOnlyFileAssets(installedTracks(projectDir))) {
|
|
2202
2257
|
if (entry.target === HARNESS_ANCHOR_FILE) continue;
|
|
2203
2258
|
if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
|
|
2204
|
-
const target =
|
|
2259
|
+
const target = join12(projectDir, entry.target);
|
|
2205
2260
|
const foreign = foreignOwnedTarget(projectDir, entry.target);
|
|
2206
2261
|
if (foreign !== null) {
|
|
2207
2262
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2208
2263
|
continue;
|
|
2209
2264
|
}
|
|
2210
|
-
if (
|
|
2211
|
-
const source =
|
|
2212
|
-
if (!
|
|
2265
|
+
if (existsSync13(target)) continue;
|
|
2266
|
+
const source = join12(templatesDir, entry.source);
|
|
2267
|
+
if (!existsSync13(source)) continue;
|
|
2213
2268
|
if (entry.target.startsWith(".claude/")) {
|
|
2214
2269
|
if (!claudeSelected) continue;
|
|
2215
2270
|
if (entry.target.startsWith(".claude/hooks/") || entry.target === ".claude/settings.json") {
|
|
@@ -2234,16 +2289,17 @@ function trackOnlyFileAssets(tracks) {
|
|
|
2234
2289
|
);
|
|
2235
2290
|
}
|
|
2236
2291
|
function syncHarnessAnchor(projectDir, templatesDir, report) {
|
|
2237
|
-
if (
|
|
2292
|
+
if (existsSync13(join12(projectDir, LEGACY_ANCHOR_FILE))) {
|
|
2238
2293
|
report.legacyAnchor = LEGACY_ANCHOR_FILE;
|
|
2239
2294
|
}
|
|
2240
|
-
const templateMd =
|
|
2241
|
-
if (!
|
|
2242
|
-
const anchor =
|
|
2243
|
-
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);
|
|
2244
2299
|
copyFileSync(templateMd, anchor);
|
|
2245
2300
|
if (existed) {
|
|
2246
2301
|
report.claudeMdUpdated = true;
|
|
2302
|
+
report.rootBlockRefreshed = upsertRootImport(projectDir);
|
|
2247
2303
|
return;
|
|
2248
2304
|
}
|
|
2249
2305
|
report.anchorCreated = true;
|
|
@@ -2251,11 +2307,16 @@ function syncHarnessAnchor(projectDir, templatesDir, report) {
|
|
|
2251
2307
|
recordAnchorBaseline(projectDir, anchor);
|
|
2252
2308
|
}
|
|
2253
2309
|
function upsertRootImport(projectDir) {
|
|
2254
|
-
const target =
|
|
2255
|
-
const existing =
|
|
2310
|
+
const target = join12(projectDir, "CLAUDE.md");
|
|
2311
|
+
const existing = existsSync13(target) ? readFileSync12(target, "utf8") : null;
|
|
2256
2312
|
const next = upsertHarnessImport(existing, {
|
|
2257
2313
|
projectName: basename4(projectDir),
|
|
2258
|
-
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
|
+
)
|
|
2259
2320
|
});
|
|
2260
2321
|
if (next === existing) return false;
|
|
2261
2322
|
writeFileSync5(target, next);
|
|
@@ -2274,7 +2335,7 @@ function recordAnchorBaseline(projectDir, anchor) {
|
|
|
2274
2335
|
...log.templates,
|
|
2275
2336
|
rootClaudeMd: {
|
|
2276
2337
|
path: HARNESS_ANCHOR_FILE,
|
|
2277
|
-
sha256: hashContent(
|
|
2338
|
+
sha256: hashContent(readFileSync12(anchor, "utf8"))
|
|
2278
2339
|
}
|
|
2279
2340
|
}
|
|
2280
2341
|
};
|
|
@@ -2316,16 +2377,16 @@ function isHarnessOwned2(ctx, rel, current) {
|
|
|
2316
2377
|
return isHarnessOwned(ctx.baseline, `${ctx.prefix}/${rel}`, current);
|
|
2317
2378
|
}
|
|
2318
2379
|
function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
2319
|
-
if (!
|
|
2380
|
+
if (!existsSync13(target) || !existsSync13(source)) return { updated: 0, backedUp: [] };
|
|
2320
2381
|
let updated = 0;
|
|
2321
2382
|
const backedUp = [];
|
|
2322
2383
|
for (const file of readdirSync3(target)) {
|
|
2323
2384
|
if (!file.endsWith(ext)) continue;
|
|
2324
|
-
const targetFile =
|
|
2325
|
-
const sourceFile =
|
|
2326
|
-
if (!
|
|
2327
|
-
const next =
|
|
2328
|
-
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");
|
|
2329
2390
|
if (current === next) continue;
|
|
2330
2391
|
if (!isHarnessOwned2(ctx, file, current)) {
|
|
2331
2392
|
backupFile(targetFile, now);
|
|
@@ -2337,7 +2398,7 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
|
2337
2398
|
return { updated, backedUp };
|
|
2338
2399
|
}
|
|
2339
2400
|
function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date(), foreignOf) {
|
|
2340
|
-
if (!
|
|
2401
|
+
if (!existsSync13(targetDir) || !existsSync13(sourceDir))
|
|
2341
2402
|
return { updated: 0, backedUp: [], skippedLinks: [], foreignOwned: [] };
|
|
2342
2403
|
let updated = 0;
|
|
2343
2404
|
const backedUp = [];
|
|
@@ -2345,27 +2406,27 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
|
|
|
2345
2406
|
const foreignOwned = [];
|
|
2346
2407
|
for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
2347
2408
|
if (!skill.isDirectory()) continue;
|
|
2348
|
-
const targetSkill =
|
|
2409
|
+
const targetSkill = join12(targetDir, skill.name);
|
|
2349
2410
|
if (occupiedByNonDirectory(targetSkill)) {
|
|
2350
2411
|
skippedLinks.push(skill.name);
|
|
2351
2412
|
continue;
|
|
2352
2413
|
}
|
|
2353
|
-
if (!
|
|
2354
|
-
for (const rel of listFilesRecursive(
|
|
2355
|
-
const targetFile =
|
|
2414
|
+
if (!existsSync13(targetSkill)) continue;
|
|
2415
|
+
for (const rel of listFilesRecursive(join12(sourceDir, skill.name))) {
|
|
2416
|
+
const targetFile = join12(targetSkill, rel);
|
|
2356
2417
|
const foreign = foreignOf(`${skill.name}/${rel}`);
|
|
2357
2418
|
if (foreign !== null) {
|
|
2358
2419
|
if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
|
|
2359
2420
|
continue;
|
|
2360
2421
|
}
|
|
2361
|
-
const next =
|
|
2362
|
-
if (!
|
|
2422
|
+
const next = readFileSync12(join12(sourceDir, skill.name, rel), "utf8");
|
|
2423
|
+
if (!existsSync13(targetFile)) {
|
|
2363
2424
|
mkdirSync3(dirname3(targetFile), { recursive: true });
|
|
2364
2425
|
writeFileSync5(targetFile, next);
|
|
2365
2426
|
updated++;
|
|
2366
2427
|
continue;
|
|
2367
2428
|
}
|
|
2368
|
-
const current =
|
|
2429
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2369
2430
|
if (current === next) continue;
|
|
2370
2431
|
const recorded = baseline.get(`${skill.name}/${rel}`);
|
|
2371
2432
|
if (recorded === void 0 || hashContent(current) !== recorded) {
|
|
@@ -2411,16 +2472,16 @@ function refreshSkillBaseline(projectDir) {
|
|
|
2411
2472
|
}
|
|
2412
2473
|
}
|
|
2413
2474
|
function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
|
|
2414
|
-
if (!
|
|
2475
|
+
if (!existsSync13(target) || !existsSync13(source)) return [];
|
|
2415
2476
|
const removed = [];
|
|
2416
2477
|
for (const file of readdirSync3(target)) {
|
|
2417
2478
|
if (!file.endsWith(ext)) continue;
|
|
2418
|
-
const sourceFile =
|
|
2419
|
-
if (!
|
|
2420
|
-
const targetFile =
|
|
2479
|
+
const sourceFile = join12(source, file);
|
|
2480
|
+
if (!existsSync13(sourceFile)) {
|
|
2481
|
+
const targetFile = join12(target, file);
|
|
2421
2482
|
if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
|
|
2422
2483
|
try {
|
|
2423
|
-
const current =
|
|
2484
|
+
const current = readFileSync12(targetFile, "utf8");
|
|
2424
2485
|
if (!isHarnessOwned2(ctx, file, current)) backupFile(targetFile, now);
|
|
2425
2486
|
unlinkSync2(targetFile);
|
|
2426
2487
|
removed.push(file);
|
|
@@ -2431,8 +2492,8 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
|
|
|
2431
2492
|
return removed;
|
|
2432
2493
|
}
|
|
2433
2494
|
function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
2434
|
-
const target =
|
|
2435
|
-
if (!
|
|
2495
|
+
const target = join12(projectDir, ".mcp-allowlist");
|
|
2496
|
+
if (!existsSync13(target)) return null;
|
|
2436
2497
|
try {
|
|
2437
2498
|
const backup = backupFile(target, now);
|
|
2438
2499
|
unlinkSync2(target);
|
|
@@ -2444,7 +2505,7 @@ function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
|
|
|
2444
2505
|
function cleanStaleHookRefs(settingsPath, claudeDir) {
|
|
2445
2506
|
let settings;
|
|
2446
2507
|
try {
|
|
2447
|
-
settings = JSON.parse(
|
|
2508
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
2448
2509
|
} catch {
|
|
2449
2510
|
return [];
|
|
2450
2511
|
}
|
|
@@ -2471,7 +2532,7 @@ function cleanStaleHookRefs(settingsPath, claudeDir) {
|
|
|
2471
2532
|
function keepHookRef(hook, claudeDir, removed) {
|
|
2472
2533
|
const relPath = projectAnchoredRef(hook?.command ?? "", claudeDir);
|
|
2473
2534
|
if (relPath === void 0) return true;
|
|
2474
|
-
const exists =
|
|
2535
|
+
const exists = existsSync13(join12(claudeDir, relPath));
|
|
2475
2536
|
if (!exists && !removed.includes(relPath)) removed.push(relPath);
|
|
2476
2537
|
return exists;
|
|
2477
2538
|
}
|
|
@@ -2498,14 +2559,14 @@ function projectAnchoredRef(command, claudeDir) {
|
|
|
2498
2559
|
function runInstall(ctx) {
|
|
2499
2560
|
const { harnessRoot, projectDir, spec } = ctx;
|
|
2500
2561
|
const mode = ctx.mode ?? "fresh";
|
|
2501
|
-
const templatesDir =
|
|
2502
|
-
if (!
|
|
2562
|
+
const templatesDir = join13(harnessRoot, "templates");
|
|
2563
|
+
if (!existsSync14(templatesDir)) {
|
|
2503
2564
|
throw new Error(`Templates dir not found: ${templatesDir}`);
|
|
2504
2565
|
}
|
|
2505
|
-
const claudeDir =
|
|
2566
|
+
const claudeDir = join13(projectDir, ".claude");
|
|
2506
2567
|
const previousLog = readInstallLog(projectDir);
|
|
2507
2568
|
const claudeWasSelected = previousLog?.spec.cli.includes("claude") ?? false;
|
|
2508
|
-
if (mode === "update" && !
|
|
2569
|
+
if (mode === "update" && !existsSync14(claudeDir) && (previousLog === null || claudeWasSelected)) {
|
|
2509
2570
|
throw new Error(
|
|
2510
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}`
|
|
2511
2572
|
);
|
|
@@ -2599,7 +2660,7 @@ function applySupersededCleanup(spec, projectDir, found) {
|
|
|
2599
2660
|
const kept = [];
|
|
2600
2661
|
for (const asset of found) {
|
|
2601
2662
|
try {
|
|
2602
|
-
unlinkSync3(
|
|
2663
|
+
unlinkSync3(join13(projectDir, asset.target));
|
|
2603
2664
|
removed.push(asset.target);
|
|
2604
2665
|
} catch {
|
|
2605
2666
|
kept.push(asset.target);
|
|
@@ -2609,9 +2670,9 @@ function applySupersededCleanup(spec, projectDir, found) {
|
|
|
2609
2670
|
}
|
|
2610
2671
|
function healStaleHookRefs(spec, projectDir) {
|
|
2611
2672
|
if (!spec.cli.includes("claude")) return [];
|
|
2612
|
-
const settingsPath =
|
|
2613
|
-
if (!
|
|
2614
|
-
return cleanStaleHookRefs(settingsPath,
|
|
2673
|
+
const settingsPath = join13(projectDir, ".claude/settings.json");
|
|
2674
|
+
if (!existsSync14(settingsPath)) return [];
|
|
2675
|
+
return cleanStaleHookRefs(settingsPath, join13(projectDir, ".claude"));
|
|
2615
2676
|
}
|
|
2616
2677
|
function resolveBackupPath(ctx, mode, claudeDir) {
|
|
2617
2678
|
const wantBackup = ctx.backup ?? (mode === "update" || mode === "reinstall");
|
|
@@ -2651,25 +2712,11 @@ function runUpdateInstall(ctx, templatesDir, backupPath) {
|
|
|
2651
2712
|
return { ...baseline, external: null, staleHookRefs: [] };
|
|
2652
2713
|
}
|
|
2653
2714
|
function buildManifestSpec(spec) {
|
|
2654
|
-
|
|
2715
|
+
return buildAssetSpec({
|
|
2655
2716
|
tracks: spec.tracks,
|
|
2656
2717
|
options: spec.options,
|
|
2657
2718
|
...spec.userOverride ? { userOverride: spec.userOverride } : {}
|
|
2658
|
-
};
|
|
2659
|
-
return {
|
|
2660
|
-
tracks: spec.tracks,
|
|
2661
|
-
withTauri: isAssetSelected("tauri-desktop", selectionCtx),
|
|
2662
|
-
// v26.55.0 — withEcc gating (ADR-016). ECC cherry-pick (agents/skills/commands) 항목 토글.
|
|
2663
|
-
// withPrune 은 ecc-plugin 사용을 전제 (이전 applyOptionRules `withEcc ||= withPrune` 의미 보존).
|
|
2664
|
-
withEcc: isAssetSelected("ecc-plugin", selectionCtx) || spec.options.withPrune,
|
|
2665
|
-
// v26.87.0 — internal bundled skills (dev-method + opt-in advisors, v26.95.0). Each id's
|
|
2666
|
-
// condition (has-dev-track vs opt-in) is applied by isAssetSelected — manifest copy + the 3
|
|
2667
|
-
// non-Claude CLI transforms gate on this filtered list, so opt-in ones install only when
|
|
2668
|
-
// wizard-checked / `--with <id>`, and any uncheck / `--without <id>` (forceExclude) drops it.
|
|
2669
|
-
selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS.filter(
|
|
2670
|
-
(id) => isAssetSelected(id, selectionCtx)
|
|
2671
|
-
)
|
|
2672
|
-
};
|
|
2719
|
+
});
|
|
2673
2720
|
}
|
|
2674
2721
|
function emptyClaudeBaseline() {
|
|
2675
2722
|
return {
|
|
@@ -2694,12 +2741,12 @@ function installCliNeutralAssets(manifestSpec, projectDir, templatesDir, baselin
|
|
|
2694
2741
|
continue;
|
|
2695
2742
|
}
|
|
2696
2743
|
if (!isCliNeutralTarget(entry.target)) continue;
|
|
2697
|
-
const source =
|
|
2698
|
-
if (!
|
|
2744
|
+
const source = join13(templatesDir, entry.source);
|
|
2745
|
+
if (!existsSync14(source)) {
|
|
2699
2746
|
result.skipped += 1;
|
|
2700
2747
|
continue;
|
|
2701
2748
|
}
|
|
2702
|
-
copyFile(source,
|
|
2749
|
+
copyFile(source, join13(projectDir, entry.target));
|
|
2703
2750
|
result.filesCopied += 1;
|
|
2704
2751
|
}
|
|
2705
2752
|
return result;
|
|
@@ -2711,9 +2758,9 @@ function backupEditedPolicyFile(entryTarget, target, source, baseline) {
|
|
|
2711
2758
|
if (!POLICY_DIRS.some(({ dir, ext }) => rel.startsWith(`${dir}/`) && rel.endsWith(ext))) {
|
|
2712
2759
|
return null;
|
|
2713
2760
|
}
|
|
2714
|
-
if (!
|
|
2715
|
-
const current =
|
|
2716
|
-
if (current ===
|
|
2761
|
+
if (!existsSync14(target)) return null;
|
|
2762
|
+
const current = readFileSync13(target, "utf-8");
|
|
2763
|
+
if (current === readFileSync13(source, "utf-8")) return null;
|
|
2717
2764
|
const recorded = baseline.get(rel);
|
|
2718
2765
|
if (recorded !== void 0 && recorded === hashContent(current)) return null;
|
|
2719
2766
|
return backupFile(target);
|
|
@@ -2726,14 +2773,14 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2726
2773
|
if (!entry.applies(manifestSpec)) {
|
|
2727
2774
|
continue;
|
|
2728
2775
|
}
|
|
2729
|
-
const target =
|
|
2776
|
+
const target = join13(projectDir, entry.target);
|
|
2730
2777
|
if (isBaselineExcluded(entry.target, baselineExcluded)) {
|
|
2731
2778
|
result.excluded.push(entry.target);
|
|
2732
|
-
if (
|
|
2779
|
+
if (existsSync14(target)) result.excludedOnDisk.push(entry.target);
|
|
2733
2780
|
continue;
|
|
2734
2781
|
}
|
|
2735
|
-
const source =
|
|
2736
|
-
if (!
|
|
2782
|
+
const source = join13(templatesDir, entry.source);
|
|
2783
|
+
if (!existsSync14(source)) {
|
|
2737
2784
|
result.skipped += 1;
|
|
2738
2785
|
continue;
|
|
2739
2786
|
}
|
|
@@ -2744,7 +2791,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2744
2791
|
}
|
|
2745
2792
|
if (entry.type === "file") {
|
|
2746
2793
|
if (entry.target === ".claude/settings.json") {
|
|
2747
|
-
const backup = backupFileIfChanged(target,
|
|
2794
|
+
const backup = backupFileIfChanged(target, readFileSync13(source, "utf-8"));
|
|
2748
2795
|
if (backup) {
|
|
2749
2796
|
result.backups.push(backup);
|
|
2750
2797
|
}
|
|
@@ -2768,20 +2815,24 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
|
|
|
2768
2815
|
}
|
|
2769
2816
|
accumulateCategory(result.categories, entry);
|
|
2770
2817
|
}
|
|
2771
|
-
const hookDir =
|
|
2772
|
-
if (
|
|
2818
|
+
const hookDir = join13(projectDir, ".claude/hooks");
|
|
2819
|
+
if (existsSync14(hookDir)) {
|
|
2773
2820
|
chmodHooksSync(hookDir);
|
|
2774
2821
|
}
|
|
2775
2822
|
writeInstalledTracks(projectDir, manifestSpec.tracks);
|
|
2776
|
-
const rootClaudeMd = writeRootClaudeMd(
|
|
2823
|
+
const rootClaudeMd = writeRootClaudeMd(
|
|
2824
|
+
projectDir,
|
|
2825
|
+
manifestSpec.tracks,
|
|
2826
|
+
manifestSpec.selectedInternalSkills ?? []
|
|
2827
|
+
);
|
|
2777
2828
|
result.rootClaudeMd = { tracks: manifestSpec.tracks, created: rootClaudeMd.created };
|
|
2778
2829
|
result.rootClaudeMdLog = harnessAnchorLog(projectDir);
|
|
2779
2830
|
return result;
|
|
2780
2831
|
}
|
|
2781
2832
|
function harnessAnchorLog(projectDir) {
|
|
2782
|
-
const anchor =
|
|
2783
|
-
if (!
|
|
2784
|
-
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")) };
|
|
2785
2836
|
}
|
|
2786
2837
|
function writeEnvironmentFiles(projectDir, tracks) {
|
|
2787
2838
|
return {
|
|
@@ -2840,7 +2891,7 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2840
2891
|
rootFiles
|
|
2841
2892
|
);
|
|
2842
2893
|
const skillFiles = collectSkillHashes(ctx.projectDir);
|
|
2843
|
-
const policyFiles = collectPolicyHashes(ctx.projectDir,
|
|
2894
|
+
const policyFiles = collectPolicyHashes(ctx.projectDir, join13(ctx.harnessRoot, "templates"));
|
|
2844
2895
|
const merged = mergeExternalFiles(ctx.projectDir, previousLog?.externalFiles, externalFiles);
|
|
2845
2896
|
writeInstallLog(ctx.projectDir, {
|
|
2846
2897
|
...log,
|
|
@@ -2856,11 +2907,11 @@ function writeInstallLogSafe(ctx, externalFiles, external, rootClaudeMdLog, prev
|
|
|
2856
2907
|
}
|
|
2857
2908
|
}
|
|
2858
2909
|
function composeAndWriteMcp(harnessRoot, projectDir, spec) {
|
|
2859
|
-
const mcpPath =
|
|
2860
|
-
const created = !
|
|
2910
|
+
const mcpPath = join13(projectDir, ".mcp.json");
|
|
2911
|
+
const created = !existsSync14(mcpPath);
|
|
2861
2912
|
const composed = composeMcpJson({
|
|
2862
|
-
templateMcpPath:
|
|
2863
|
-
trackMapPath:
|
|
2913
|
+
templateMcpPath: join13(harnessRoot, "templates/mcp.json"),
|
|
2914
|
+
trackMapPath: join13(harnessRoot, "templates/track-mcp-map.tsv"),
|
|
2864
2915
|
existingPath: mcpPath,
|
|
2865
2916
|
tracks: spec.tracks
|
|
2866
2917
|
});
|
|
@@ -2913,16 +2964,20 @@ function accumulateCategory(cats, entry) {
|
|
|
2913
2964
|
}
|
|
2914
2965
|
}
|
|
2915
2966
|
function writeInstalledTracks(projectDir, tracks) {
|
|
2916
|
-
const path =
|
|
2967
|
+
const path = join13(projectDir, ".claude/.installed-tracks");
|
|
2917
2968
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
2918
2969
|
const sorted = [...new Set(tracks)].sort().join("\n");
|
|
2919
2970
|
writeFileSync6(path, `${sorted}
|
|
2920
2971
|
`);
|
|
2921
2972
|
}
|
|
2922
|
-
function writeRootClaudeMd(projectDir, tracks) {
|
|
2923
|
-
const target =
|
|
2924
|
-
const existing =
|
|
2925
|
-
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
|
+
});
|
|
2926
2981
|
if (content !== existing) {
|
|
2927
2982
|
writeFileSync6(target, content);
|
|
2928
2983
|
}
|
|
@@ -2931,7 +2986,7 @@ function writeRootClaudeMd(projectDir, tracks) {
|
|
|
2931
2986
|
function chmodHooksSync(hookDir) {
|
|
2932
2987
|
for (const file of listHookFiles(hookDir)) {
|
|
2933
2988
|
try {
|
|
2934
|
-
|
|
2989
|
+
chmodSync3(file, 493);
|
|
2935
2990
|
} catch {
|
|
2936
2991
|
}
|
|
2937
2992
|
}
|
|
@@ -3007,8 +3062,9 @@ function renderInstallHeader(log, spec, mode) {
|
|
|
3007
3062
|
for (const [cat, ids] of groupAssetsByCategory(finalAssets)) {
|
|
3008
3063
|
log(` ${c.dim(`\xB7 ${cat}:`)} ${ids.join(", ")}`);
|
|
3009
3064
|
}
|
|
3065
|
+
const assetSpec = buildManifestSpec(spec);
|
|
3010
3066
|
const cost = formatResidentCostLine(
|
|
3011
|
-
residentCost(buildManifest(
|
|
3067
|
+
residentCost(buildManifest(assetSpec).filter((e2) => e2.applies(assetSpec))),
|
|
3012
3068
|
summarizeContextCost(finalAssets).unmeasuredCount
|
|
3013
3069
|
);
|
|
3014
3070
|
if (cost) log(` ${c.dim(`\xB7 ${cost}`)}`);
|
|
@@ -3097,8 +3153,8 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
3097
3153
|
log(
|
|
3098
3154
|
assetRow(
|
|
3099
3155
|
"success",
|
|
3100
|
-
".agents/skills/<id>/
|
|
3101
|
-
`${report.codex.skillFiles
|
|
3156
|
+
".agents/skills/<id>/",
|
|
3157
|
+
`${countSkillDirs(report.codex.skillFiles)} skills`
|
|
3102
3158
|
)
|
|
3103
3159
|
);
|
|
3104
3160
|
}
|
|
@@ -3116,7 +3172,7 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
3116
3172
|
assetRow(
|
|
3117
3173
|
"success",
|
|
3118
3174
|
".agents/skills/",
|
|
3119
|
-
`${report.opencode.skillFiles
|
|
3175
|
+
`${countSkillDirs(report.opencode.skillFiles)} dev-method skills (codex\xB7antigravity \uC640 \uAC19\uC740 \uC790\uB9AC)`
|
|
3120
3176
|
)
|
|
3121
3177
|
);
|
|
3122
3178
|
}
|
|
@@ -3138,8 +3194,8 @@ function renderCliArtifacts(log, spec, report) {
|
|
|
3138
3194
|
log(
|
|
3139
3195
|
assetRow(
|
|
3140
3196
|
"success",
|
|
3141
|
-
".agents/skills/<id>/
|
|
3142
|
-
`${report.antigravity.skillFiles
|
|
3197
|
+
".agents/skills/<id>/",
|
|
3198
|
+
`${countSkillDirs(report.antigravity.skillFiles)} skills`
|
|
3143
3199
|
)
|
|
3144
3200
|
);
|
|
3145
3201
|
}
|
|
@@ -3205,15 +3261,25 @@ function renderFinalSummary(log, spec, report, fromWizard) {
|
|
|
3205
3261
|
log(infoRow("NEXT", `Open ${c.bold(label)} \u2014 installed rules & skills are now active`));
|
|
3206
3262
|
const scaffoldFiles = scaffoldFilesForCli(spec.cli);
|
|
3207
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` : "";
|
|
3208
3266
|
log(
|
|
3209
3267
|
infoRow(
|
|
3210
3268
|
"FILL",
|
|
3211
|
-
`${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}`
|
|
3212
3270
|
)
|
|
3213
3271
|
);
|
|
3214
3272
|
}
|
|
3215
3273
|
log("");
|
|
3216
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
|
+
}
|
|
3217
3283
|
function scaffoldFilesForCli(cli2) {
|
|
3218
3284
|
const files = [];
|
|
3219
3285
|
if (cli2.includes("claude")) {
|
|
@@ -3280,6 +3346,15 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3280
3346
|
if (baseline.updateMode.rootImportAdded) {
|
|
3281
3347
|
log(assetRow("success", "CLAUDE.md", `${HARNESS_IMPORT_LINE} import added`));
|
|
3282
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
|
+
}
|
|
3283
3358
|
if (baseline.updateMode.legacyAnchor) {
|
|
3284
3359
|
log(
|
|
3285
3360
|
assetRow(
|
|
@@ -3325,6 +3400,15 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3325
3400
|
)
|
|
3326
3401
|
);
|
|
3327
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
|
+
}
|
|
3328
3412
|
if (baseline.updateMode.policyBackedUp.length > 0) {
|
|
3329
3413
|
log(
|
|
3330
3414
|
assetRow(
|
|
@@ -3378,13 +3462,37 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3378
3462
|
log(assetRow("skip", "external skills", `${meta} \xB7 \uB2E4\uC74C update \uC5D0\uC11C \uB2E4\uC2DC \uC2DC\uB3C4\uD55C\uB2E4`));
|
|
3379
3463
|
}
|
|
3380
3464
|
if (baseline.updateMode.externalSkillsNotInCatalog.length > 0) {
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
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
|
+
}
|
|
3388
3496
|
}
|
|
3389
3497
|
if (baseline.updateMode.externalSkillsUnknown) {
|
|
3390
3498
|
log(
|
|
@@ -3429,12 +3537,7 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3429
3537
|
);
|
|
3430
3538
|
}
|
|
3431
3539
|
if (cats.hooks.length > 0) {
|
|
3432
|
-
phase1Row(
|
|
3433
|
-
"hooks",
|
|
3434
|
-
cats.hooks.length,
|
|
3435
|
-
"session-start \xB7 protect-files \xB7 task-brief-nudge",
|
|
3436
|
-
cats.hooks
|
|
3437
|
-
);
|
|
3540
|
+
phase1Row("hooks", cats.hooks.length, "session-start \xB7 protect-files", cats.hooks);
|
|
3438
3541
|
}
|
|
3439
3542
|
if (cats.commands > 0) {
|
|
3440
3543
|
phase1Row("commands", cats.commands, "slash commands");
|
|
@@ -3443,7 +3546,7 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
|
|
|
3443
3546
|
phase1Row(
|
|
3444
3547
|
"skills",
|
|
3445
3548
|
cats.skills.length,
|
|
3446
|
-
"
|
|
3549
|
+
"deep-research \xB7 ui-visual-review \xB7 eval-harness (modified)",
|
|
3447
3550
|
cats.skills
|
|
3448
3551
|
);
|
|
3449
3552
|
}
|
|
@@ -3771,8 +3874,8 @@ function registerInstallCommand(cli2) {
|
|
|
3771
3874
|
|
|
3772
3875
|
// src/commands/list.ts
|
|
3773
3876
|
init_esm_shims();
|
|
3774
|
-
import { existsSync as
|
|
3775
|
-
import { join as
|
|
3877
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
3878
|
+
import { join as join14, resolve as resolve3 } from "path";
|
|
3776
3879
|
function listAction(options = {}, deps = {}) {
|
|
3777
3880
|
const log = deps.log ?? console.log;
|
|
3778
3881
|
const err = deps.err ?? console.error;
|
|
@@ -3835,8 +3938,8 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3835
3938
|
const rows = [` ${c.dim(dirs.join(" "))}`];
|
|
3836
3939
|
const rootMd = log.templates.rootClaudeMd;
|
|
3837
3940
|
if (rootMd) {
|
|
3838
|
-
const path =
|
|
3839
|
-
const modified =
|
|
3941
|
+
const path = join14(projectDir, rootMd.path);
|
|
3942
|
+
const modified = existsSync15(path) && hashContent(readFileSync14(path, "utf8")) !== rootMd.sha256;
|
|
3840
3943
|
rows.push(
|
|
3841
3944
|
modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
|
|
3842
3945
|
);
|
|
@@ -3844,7 +3947,7 @@ function formatTemplateRows(log, projectDir) {
|
|
|
3844
3947
|
return rows;
|
|
3845
3948
|
}
|
|
3846
3949
|
function formatRootFileRows(rootFiles, projectDir) {
|
|
3847
|
-
const present = rootFiles.filter((f) =>
|
|
3950
|
+
const present = rootFiles.filter((f) => existsSync15(join14(projectDir, f.path)));
|
|
3848
3951
|
const width = Math.max(0, ...present.map((f) => f.path.length));
|
|
3849
3952
|
return present.map(
|
|
3850
3953
|
(f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(f.change === "created" ? "\uC0DD\uC131" : "\uBCD1\uD569")} ${c.dim(f.notes.join(" / "))}`
|
|
@@ -3862,15 +3965,15 @@ function registerListCommand(cli2) {
|
|
|
3862
3965
|
init_esm_shims();
|
|
3863
3966
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3864
3967
|
import {
|
|
3865
|
-
existsSync as
|
|
3968
|
+
existsSync as existsSync16,
|
|
3866
3969
|
lstatSync as lstatSync2,
|
|
3867
3970
|
readdirSync as readdirSync5,
|
|
3868
|
-
readFileSync as
|
|
3971
|
+
readFileSync as readFileSync15,
|
|
3869
3972
|
rmdirSync,
|
|
3870
3973
|
rmSync,
|
|
3871
3974
|
writeFileSync as writeFileSync7
|
|
3872
3975
|
} from "fs";
|
|
3873
|
-
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";
|
|
3874
3977
|
|
|
3875
3978
|
// src/uninstall-interactive.ts
|
|
3876
3979
|
init_esm_shims();
|
|
@@ -5110,7 +5213,7 @@ function settleLog(ctx, io) {
|
|
|
5110
5213
|
return true;
|
|
5111
5214
|
}
|
|
5112
5215
|
} else {
|
|
5113
|
-
io.rm(
|
|
5216
|
+
io.rm(join15(projectDir, INSTALL_LOG_DIR));
|
|
5114
5217
|
io.rm(legacyInstallLogPath(projectDir));
|
|
5115
5218
|
io.log(` ${status.success("install log removed")}`);
|
|
5116
5219
|
}
|
|
@@ -5160,7 +5263,7 @@ function advisoryLines(plan, projectDir, rootFiles) {
|
|
|
5160
5263
|
return lines;
|
|
5161
5264
|
}
|
|
5162
5265
|
function rootFileAdvisoryLines(rootFiles, projectDir) {
|
|
5163
|
-
const present = rootFiles.filter((f) =>
|
|
5266
|
+
const present = rootFiles.filter((f) => existsSync16(join15(projectDir, f.path)));
|
|
5164
5267
|
if (present.length === 0) return [];
|
|
5165
5268
|
return [
|
|
5166
5269
|
"",
|
|
@@ -5261,16 +5364,16 @@ function buildGlobalAdvisoryCmd(asset) {
|
|
|
5261
5364
|
}
|
|
5262
5365
|
}
|
|
5263
5366
|
function removeTemplates(log, projectDir, rm) {
|
|
5264
|
-
rm(
|
|
5265
|
-
if (log.templates.codexDir) rm(
|
|
5266
|
-
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));
|
|
5267
5370
|
const external = removeExternalFiles(log, projectDir, rm);
|
|
5268
5371
|
const importStripped = stripRootImport(projectDir);
|
|
5269
5372
|
const rootMd = log.templates.rootClaudeMd;
|
|
5270
5373
|
if (rootMd) {
|
|
5271
5374
|
if (rootClaudeMdModified(log, projectDir))
|
|
5272
5375
|
return { rootClaudeMdKept: true, importStripped, external };
|
|
5273
|
-
rm(
|
|
5376
|
+
rm(join15(projectDir, rootMd.path));
|
|
5274
5377
|
}
|
|
5275
5378
|
return { rootClaudeMdKept: false, importStripped, external };
|
|
5276
5379
|
}
|
|
@@ -5278,17 +5381,17 @@ function removeExternalFiles(log, projectDir, rm) {
|
|
|
5278
5381
|
const removed = [];
|
|
5279
5382
|
const kept = [];
|
|
5280
5383
|
for (const { path, sha256 } of log.externalFiles ?? []) {
|
|
5281
|
-
const abs =
|
|
5282
|
-
if (!
|
|
5384
|
+
const abs = join15(projectDir, path);
|
|
5385
|
+
if (!existsSync16(abs)) continue;
|
|
5283
5386
|
if (!lstatSync2(abs).isFile()) continue;
|
|
5284
|
-
if (hashContent(
|
|
5387
|
+
if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
|
|
5285
5388
|
kept.push(path);
|
|
5286
5389
|
continue;
|
|
5287
5390
|
}
|
|
5288
5391
|
rm(abs);
|
|
5289
5392
|
removed.push(path);
|
|
5290
5393
|
}
|
|
5291
|
-
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(
|
|
5394
|
+
for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(join15(projectDir, path)));
|
|
5292
5395
|
return { removed, kept };
|
|
5293
5396
|
}
|
|
5294
5397
|
function pruneEmptyDirsUpward(projectDir, startDir) {
|
|
@@ -5310,7 +5413,7 @@ function stripRootImport(projectDir) {
|
|
|
5310
5413
|
const stripped = current === null ? null : stripHarnessImport(current);
|
|
5311
5414
|
if (stripped === null) return false;
|
|
5312
5415
|
try {
|
|
5313
|
-
writeFileSync7(
|
|
5416
|
+
writeFileSync7(join15(projectDir, "CLAUDE.md"), stripped, "utf8");
|
|
5314
5417
|
return true;
|
|
5315
5418
|
} catch {
|
|
5316
5419
|
return false;
|
|
@@ -5320,9 +5423,9 @@ function previewExternalLines(installLog, projectDir) {
|
|
|
5320
5423
|
const lines = [];
|
|
5321
5424
|
let removable = 0;
|
|
5322
5425
|
for (const { path, sha256 } of installLog.externalFiles ?? []) {
|
|
5323
|
-
const abs =
|
|
5324
|
-
if (!
|
|
5325
|
-
if (hashContent(
|
|
5426
|
+
const abs = join15(projectDir, path);
|
|
5427
|
+
if (!existsSync16(abs)) continue;
|
|
5428
|
+
if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
|
|
5326
5429
|
lines.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
|
|
5327
5430
|
continue;
|
|
5328
5431
|
}
|
|
@@ -5348,10 +5451,10 @@ function hasRootImport(projectDir) {
|
|
|
5348
5451
|
return current !== null && stripHarnessImport(current) !== null;
|
|
5349
5452
|
}
|
|
5350
5453
|
function readRootClaudeMd(projectDir) {
|
|
5351
|
-
const path =
|
|
5352
|
-
if (!
|
|
5454
|
+
const path = join15(projectDir, "CLAUDE.md");
|
|
5455
|
+
if (!existsSync16(path)) return null;
|
|
5353
5456
|
try {
|
|
5354
|
-
return
|
|
5457
|
+
return readFileSync15(path, "utf8");
|
|
5355
5458
|
} catch {
|
|
5356
5459
|
return null;
|
|
5357
5460
|
}
|
|
@@ -5359,9 +5462,9 @@ function readRootClaudeMd(projectDir) {
|
|
|
5359
5462
|
function rootClaudeMdModified(log, projectDir) {
|
|
5360
5463
|
const rootMd = log.templates.rootClaudeMd;
|
|
5361
5464
|
if (!rootMd) return false;
|
|
5362
|
-
const path =
|
|
5363
|
-
if (!
|
|
5364
|
-
return hashContent(
|
|
5465
|
+
const path = join15(projectDir, rootMd.path);
|
|
5466
|
+
if (!existsSync16(path)) return false;
|
|
5467
|
+
return hashContent(readFileSync15(path, "utf8")) !== rootMd.sha256;
|
|
5365
5468
|
}
|
|
5366
5469
|
function formatTemplateList(log) {
|
|
5367
5470
|
const items = [log.templates.claudeDir];
|
|
@@ -5373,7 +5476,7 @@ function defaultSpawn2(cmd, args) {
|
|
|
5373
5476
|
return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
|
|
5374
5477
|
}
|
|
5375
5478
|
function defaultRm(path) {
|
|
5376
|
-
if (
|
|
5479
|
+
if (existsSync16(path)) {
|
|
5377
5480
|
rmSync(path, { recursive: true, force: true });
|
|
5378
5481
|
}
|
|
5379
5482
|
}
|
|
@@ -5421,8 +5524,8 @@ import { resolve as resolve5 } from "path";
|
|
|
5421
5524
|
|
|
5422
5525
|
// src/state.ts
|
|
5423
5526
|
init_esm_shims();
|
|
5424
|
-
import { existsSync as
|
|
5425
|
-
import { join as
|
|
5527
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
5528
|
+
import { join as join16 } from "path";
|
|
5426
5529
|
var META_FILE = ".claude/.installed-tracks";
|
|
5427
5530
|
var LEGACY_SIGNATURES = [
|
|
5428
5531
|
{ rule: "htmx.md", track: "ssr-htmx" },
|
|
@@ -5432,11 +5535,11 @@ var LEGACY_SIGNATURES = [
|
|
|
5432
5535
|
{ rule: "cli-development.md", track: "tooling" }
|
|
5433
5536
|
];
|
|
5434
5537
|
function detectInstallState(projectDir) {
|
|
5435
|
-
const claudeDir =
|
|
5436
|
-
const hasClaudeDir =
|
|
5538
|
+
const claudeDir = join16(projectDir, ".claude");
|
|
5539
|
+
const hasClaudeDir = existsSync17(claudeDir);
|
|
5437
5540
|
if (hasClaudeDir) {
|
|
5438
|
-
const metaPath =
|
|
5439
|
-
if (
|
|
5541
|
+
const metaPath = join16(projectDir, META_FILE);
|
|
5542
|
+
if (existsSync17(metaPath)) {
|
|
5440
5543
|
const tracks2 = readMetafile(metaPath);
|
|
5441
5544
|
return { state: "existing", tracks: tracks2, source: "metafile", hasClaudeDir: true };
|
|
5442
5545
|
}
|
|
@@ -5458,7 +5561,7 @@ function tracksFromLog(tracks) {
|
|
|
5458
5561
|
return [...new Set(tracks.filter(isTrack))].sort();
|
|
5459
5562
|
}
|
|
5460
5563
|
function readMetafile(path) {
|
|
5461
|
-
const raw =
|
|
5564
|
+
const raw = readFileSync16(path, "utf8");
|
|
5462
5565
|
const seen = /* @__PURE__ */ new Set();
|
|
5463
5566
|
for (const line of raw.split(/\s+/)) {
|
|
5464
5567
|
const trimmed = line.trim();
|
|
@@ -5469,13 +5572,13 @@ function readMetafile(path) {
|
|
|
5469
5572
|
return [...seen].sort();
|
|
5470
5573
|
}
|
|
5471
5574
|
function inferFromLegacySignatures(projectDir) {
|
|
5472
|
-
const rulesDir =
|
|
5473
|
-
if (!
|
|
5575
|
+
const rulesDir = join16(projectDir, ".claude/rules");
|
|
5576
|
+
if (!existsSync17(rulesDir)) {
|
|
5474
5577
|
return [];
|
|
5475
5578
|
}
|
|
5476
5579
|
const found = /* @__PURE__ */ new Set();
|
|
5477
5580
|
for (const sig of LEGACY_SIGNATURES) {
|
|
5478
|
-
if (
|
|
5581
|
+
if (existsSync17(join16(rulesDir, sig.rule))) {
|
|
5479
5582
|
found.add(sig.track);
|
|
5480
5583
|
}
|
|
5481
5584
|
}
|
|
@@ -5605,7 +5708,7 @@ var INSTALL_TARGET_PAGES = [
|
|
|
5605
5708
|
// 2026-08-16 — 트랙이 고르는 자산을 **맨 앞에서** 전부 보여준다. 그전까지 이 항목들은 화면에
|
|
5606
5709
|
// 한 번도 안 나온 채 깔렸고, 사용자는 설치가 끝난 뒤 요약에서 처음 봤다.
|
|
5607
5710
|
//
|
|
5608
|
-
// 2페이지로 나눈 근거는 실측이다: 최대 트랙(`full`)이 32항목(rules 6 · agents 9 · hooks 3 ·
|
|
5711
|
+
// 2페이지로 나눈 근거는 실측이다: 최대 트랙(`full`)이 32항목(당시 실측 — rules 6 · agents 9 · hooks 3 ·
|
|
5609
5712
|
// skills 14)이라 clack groupMultiselect 의 "페이지당 ≤ ~30행" 제약을 한 페이지로는 못 지킨다.
|
|
5610
5713
|
// 가르는 축은 역할이다 — 정책·가드가 한쪽, 실행 능력이 다른 쪽.
|
|
5611
5714
|
{ label: "Track baseline \u2014 Rules & Hooks (\uC815\uCC45\xB7\uAC00\uB4DC)", cats: [], baseline: ["rules", "hooks"] },
|
|
@@ -6149,10 +6252,11 @@ function formatSummary(spec) {
|
|
|
6149
6252
|
for (const [cat, ids] of groupAssetsByCategory(finalAssets)) {
|
|
6150
6253
|
lines.push(` \xB7 ${cat}: ${ids.join(", ")}`);
|
|
6151
6254
|
}
|
|
6255
|
+
const assetSpec = buildManifestSpec(spec);
|
|
6152
6256
|
const cost = formatResidentCostLine(
|
|
6153
6257
|
residentCost(
|
|
6154
|
-
buildManifest(
|
|
6155
|
-
(e2) => e2.applies(
|
|
6258
|
+
buildManifest(assetSpec).filter(
|
|
6259
|
+
(e2) => e2.applies(assetSpec) && !isBaselineExcluded(e2.target, baselineExcluded)
|
|
6156
6260
|
)
|
|
6157
6261
|
),
|
|
6158
6262
|
summarizeContextCost(finalAssets).unmeasuredCount
|