@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.
Files changed (59) hide show
  1. package/README.ko.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/{chunk-NKBUDHPC.js → chunk-3QBHZUVB.js} +99 -50
  4. package/dist/chunk-3QBHZUVB.js.map +1 -0
  5. package/dist/index.js +389 -274
  6. package/dist/index.js.map +1 -1
  7. package/dist/trust-tier-drift.js +1 -1
  8. package/package.json +1 -1
  9. package/templates/CLAUDE.md +145 -164
  10. package/templates/agents/build-error-resolver.md +1 -1
  11. package/templates/agents/plan-checker.md +1 -1
  12. package/templates/agents/reviewer.md +4 -5
  13. package/templates/antigravity/AGENTS.md.template +3 -23
  14. package/templates/codex/AGENTS.md.template +5 -56
  15. package/templates/hooks/protect-files.sh +4 -0
  16. package/templates/opencode/AGENTS.md.template +4 -52
  17. package/templates/opencode/opencode.json.template +0 -8
  18. package/templates/rules/change-management.md +0 -1
  19. package/templates/rules/cli-development.md +1 -1
  20. package/templates/rules/doc-governance.md +2 -0
  21. package/templates/rules/git-policy.md +1 -1
  22. package/templates/rules/ship-checklist.md +3 -3
  23. package/templates/rules/test-policy.md +3 -8
  24. package/templates/settings.json +1 -16
  25. package/templates/skills/agent-introspection-debugging/SKILL.md +1 -1
  26. package/templates/skills/audit-harness-fit/README.md +113 -0
  27. package/templates/skills/audit-harness-fit/SKILL.md +64 -433
  28. package/templates/skills/audit-harness-fit/evals/scenarios.yaml +222 -0
  29. package/templates/skills/audit-harness-fit/references/apply.md +66 -0
  30. package/templates/skills/audit-harness-fit/references/audit.md +160 -0
  31. package/templates/skills/audit-harness-fit/references/populate.md +74 -0
  32. package/templates/skills/audit-harness-fit/references/verification.md +123 -0
  33. package/templates/skills/compaction-handoff/SKILL.md +2 -2
  34. package/templates/skills/model-orchestration/SKILL.md +7 -0
  35. package/templates/skills/natural-korean/SKILL.md +45 -0
  36. package/templates/skills/north-star/references/roadmap-method.md +2 -2
  37. package/templates/skills/{task-brief → objective-brief}/SKILL.md +17 -16
  38. package/templates/skills/recurrence-prevention/SKILL.md +16 -16
  39. package/dist/chunk-NKBUDHPC.js.map +0 -1
  40. package/templates/agents/code-reviewer.md +0 -237
  41. package/templates/agents/security-reviewer.md +0 -108
  42. package/templates/hooks/task-brief-nudge.sh +0 -57
  43. package/templates/skills/audit-harness-fit/references/official-criteria.md +0 -367
  44. package/templates/skills/continuous-learning-v2/SKILL.md +0 -361
  45. package/templates/skills/continuous-learning-v2/agents/observer-loop.sh +0 -362
  46. package/templates/skills/continuous-learning-v2/agents/observer.md +0 -189
  47. package/templates/skills/continuous-learning-v2/agents/session-guardian.sh +0 -150
  48. package/templates/skills/continuous-learning-v2/agents/start-observer.sh +0 -252
  49. package/templates/skills/continuous-learning-v2/config.json +0 -8
  50. package/templates/skills/continuous-learning-v2/hooks/observe.sh +0 -585
  51. package/templates/skills/continuous-learning-v2/scripts/detect-project.sh +0 -322
  52. package/templates/skills/continuous-learning-v2/scripts/instinct-cli.py +0 -1956
  53. package/templates/skills/continuous-learning-v2/scripts/lib/homunculus-dir.sh +0 -31
  54. package/templates/skills/continuous-learning-v2/scripts/migrate-homunculus.sh +0 -68
  55. package/templates/skills/continuous-learning-v2/scripts/test_parse_instinct.py +0 -1420
  56. package/templates/skills/humanize-korean/SKILL.md +0 -228
  57. package/templates/skills/spec-scaling/SKILL.md +0 -89
  58. package/templates/skills/strategic-compact/SKILL.md +0 -145
  59. 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-NKBUDHPC.js";
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.150.0",
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 chmodSync2,
984
- existsSync as existsSync13,
988
+ chmodSync as chmodSync3,
989
+ existsSync as existsSync14,
985
990
  mkdirSync as mkdirSync4,
986
991
  readdirSync as readdirSync4,
987
- readFileSync as readFileSync12,
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 join12, resolve } from "path";
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 existsSync4, readFileSync as readFileSync3 } from "fs";
1037
- import { basename, join as join4 } from "path";
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 existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
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 join2 } from "path";
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(join2(projectDir, slot))) return slot;
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(join2(projectDir, cursor))) return cursor;
1140
+ if (occupiedByNonDirectory(join3(projectDir, cursor))) return cursor;
1104
1141
  }
1105
- const leaf = lstatSync(join2(projectDir, rel), { throwIfNoEntry: false });
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 (!existsSync2(absPath)) {
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 = readFileSync(absPath, "utf8");
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 existsSync3, readFileSync as readFileSync2 } from "fs";
1162
- import { join as join3 } from "path";
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 = join3(harnessRoot, "templates/rules", `${name}.md`);
1173
- if (!existsSync3(src)) continue;
1174
- ported.push({ name, body: stripClaudeFrontmatter(readFileSync2(src, "utf8")) });
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 = join4(projectDir, ".agents", "rules", `${rule.name}.md`);
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
- for (const id of selectedInternalSkills) {
1207
- const src = join4(harnessRoot, "templates/skills", id, "SKILL.md");
1208
- if (!existsSync4(src)) {
1209
- continue;
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 = join4(harnessRoot, "templates/CLAUDE.md");
1224
- const templatePath = join4(harnessRoot, "templates/antigravity/AGENTS.md.template");
1225
- if (!existsSync4(claudeMdPath) || !existsSync4(templatePath)) {
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 = readFileSync3(claudeMdPath, "utf8");
1229
- const template = readFileSync3(templatePath, "utf8");
1230
- const target = join4(projectDir, ".agents", "rules", "uzys-harness.md");
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
- projectContext: renderFillScaffold("antigravity-rule")
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 join5 } from "path";
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 existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
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 = existsSync5(configPath) ? readFileSync4(configPath, "utf8") : "";
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 ?? join5(homedir(), ".codex");
1279
- const configPath = join5(codexHome, "config.toml");
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 existsSync6, readFileSync as readFileSync5 } from "fs";
1293
- import { basename as basename2, join as join6 } from "path";
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(join6(harnessRoot, "templates/CLAUDE.md"));
1371
- const agentsTemplate = readRequired(join6(harnessRoot, "templates/codex/AGENTS.md.template"));
1372
- const configTemplate = readRequired(join6(harnessRoot, "templates/codex/config.toml.template"));
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(join6(harnessRoot, ".mcp.json"));
1375
- const agentsMdPath = join6(projectDir, "AGENTS.md");
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
- projectContext: renderFillScaffold("agents-md"),
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 = join6(projectDir, ".codex/config.toml");
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 = join6(projectDir, ".codex/hooks");
1437
+ const hookDir = join7(projectDir, ".codex/hooks");
1397
1438
  const hookFiles = [];
1398
1439
  for (const hook of HOOK_NAMES) {
1399
- const src = join6(harnessRoot, "templates/hooks", `${hook}.sh`);
1400
- if (!existsSync6(src)) {
1440
+ const src = join7(harnessRoot, "templates/hooks", `${hook}.sh`);
1441
+ if (!existsSync7(src)) {
1401
1442
  continue;
1402
1443
  }
1403
- const ported = readFileSync5(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
1404
- const target = join6(hookDir, `${hook}.sh`);
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
- chmodSync(target, 493);
1447
+ chmodSync2(target, 493);
1407
1448
  hookFiles.push(target);
1408
1449
  }
1409
- const skillFiles = [];
1410
- for (const id of selectedInternalSkills) {
1411
- const src = join6(harnessRoot, "templates/skills", id, "SKILL.md");
1412
- if (!existsSync6(src)) {
1413
- continue;
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 (!existsSync6(path)) {
1465
+ if (!existsSync7(path)) {
1429
1466
  throw new Error(`Codex transform: required source missing: ${path}`);
1430
1467
  }
1431
- return readFileSync5(path, "utf8");
1468
+ return readFileSync6(path, "utf8");
1432
1469
  }
1433
1470
  function readOptionalJson(path) {
1434
- if (!existsSync6(path)) {
1471
+ if (!existsSync7(path)) {
1435
1472
  return null;
1436
1473
  }
1437
1474
  try {
1438
- return JSON.parse(readFileSync5(path, "utf8"));
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 existsSync7, readdirSync, readFileSync as readFileSync6, unlinkSync } from "fs";
1447
- import { basename as basename3, join as join7 } from "path";
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(join7(harnessRoot, "templates/CLAUDE.md"));
1491
- const agentsTemplate = readRequired2(join7(harnessRoot, "templates/opencode/AGENTS.md.template"));
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
- join7(harnessRoot, "templates/opencode/opencode.json.template")
1530
+ join8(harnessRoot, "templates/opencode/opencode.json.template")
1494
1531
  );
1495
1532
  const projectName = basename3(projectDir);
1496
- const mcp = readOptionalJson2(join7(harnessRoot, ".mcp.json"));
1533
+ const mcp = readOptionalJson2(join8(harnessRoot, ".mcp.json"));
1497
1534
  ensureDir(projectDir);
1498
- const agentsMdPath = join7(projectDir, "AGENTS.md");
1535
+ const agentsMdPath = join8(projectDir, "AGENTS.md");
1499
1536
  const agentsMdOut = renderAgentsMd2({
1500
1537
  template: agentsTemplate,
1501
1538
  claudeMd,
1502
1539
  projectName,
1503
- projectContext: renderFillScaffold("agents-md"),
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 = join7(projectDir, "opencode.json");
1551
+ const opencodeJsonPath = join8(projectDir, "opencode.json");
1511
1552
  writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
1512
- const skillFiles = [];
1513
- for (const id of selectedInternalSkills) {
1514
- const src = join7(harnessRoot, "templates/skills", id, "SKILL.md");
1515
- if (!existsSync7(src)) {
1516
- continue;
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 = join7(projectDir, ".opencode/commands");
1524
- if (existsSync7(cmdDir)) {
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 (!existsSync7(join7(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
1529
- const victim = join7(cmdDir, entry);
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 (!existsSync7(path)) {
1584
+ if (!existsSync8(path)) {
1548
1585
  throw new Error(`OpenCode transform: required source missing: ${path}`);
1549
1586
  }
1550
- return readFileSync6(path, "utf8");
1587
+ return readFileSync7(path, "utf8");
1551
1588
  }
1552
1589
  function readOptionalJson2(path) {
1553
- if (!existsSync7(path)) {
1590
+ if (!existsSync8(path)) {
1554
1591
  return null;
1555
1592
  }
1556
1593
  try {
1557
- return JSON.parse(readFileSync6(path, "utf8"));
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 existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
1650
- import { join as join8 } from "path";
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 = join8(projectDir, ".env.example");
1688
- if (existsSync8(path)) {
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 = join8(projectDir, ".gitignore");
1696
- if (!existsSync8(path)) {
1732
+ const path = join9(projectDir, ".gitignore");
1733
+ if (!existsSync9(path)) {
1697
1734
  return false;
1698
1735
  }
1699
- const content = readFileSync7(path, "utf8");
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 = join8(projectDir, ".gitignore");
1714
- if (!existsSync8(path)) {
1750
+ const path = join9(projectDir, ".gitignore");
1751
+ if (!existsSync9(path)) {
1715
1752
  return [];
1716
1753
  }
1717
- const content = readFileSync7(path, "utf8");
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 existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
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 join9 } from "path";
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 = join9(ctx.harnessRoot, method.script);
1815
- if (!existsSync9(scriptPath)) {
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 = join9(homedir2(), ".claude/plugins/cache", marketplaceShort, plugin);
1946
- if (!existsSync9(cacheBase)) return void 0;
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 = join9(npmRoot, method.pkg, "package.json");
1954
- if (!existsSync9(pkgJson)) return void 0;
1955
- const parsed = JSON.parse(readFileSync8(pkgJson, "utf8"));
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 existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
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(readFileSync9(opts.templateMcpPath, "utf8"));
2034
- const merged = opts.existingPath && existsSync10(opts.existingPath) ? mergeUserBase(base, opts.existingPath) : base;
2035
- const mapRaw = existsSync10(opts.trackMapPath) ? readFileSync9(opts.trackMapPath, "utf8") : "";
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(readFileSync9(existingPath, "utf8"));
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 existsSync11, readFileSync as readFileSync10 } from "fs";
2058
- import { join as join10 } from "path";
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 = join10(projectDir, entry.target);
2083
- if (!existsSync11(abs)) continue;
2084
- if (!isHarnessOwned(baseline, key, readFileSync10(abs, "utf-8"))) continue;
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 existsSync12,
2131
+ existsSync as existsSync13,
2095
2132
  mkdirSync as mkdirSync3,
2096
2133
  readdirSync as readdirSync3,
2097
- readFileSync as readFileSync11,
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 join11 } from "path";
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 = join11(projectDir, ".claude");
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 = join11(claudeDir, dir);
2144
- const source = join11(templatesDir, dir);
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
- join11(claudeDir, "skills"),
2155
- join11(templatesDir, "skills"),
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 (existsSync12(claudeDir) && (installLog === null || installLog.spec.cli.includes("claude"))) {
2204
+ if (existsSync13(claudeDir) && (installLog === null || installLog.spec.cli.includes("claude"))) {
2166
2205
  syncHarnessAnchor(projectDir, templatesDir, report);
2167
2206
  }
2168
- const settingsPath = join11(claudeDir, "settings.json");
2169
- if (existsSync12(settingsPath)) {
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 = skillRefresh.notInCatalog;
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 = join11(projectDir, entry.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 (existsSync12(target)) continue;
2212
- const source = join11(templatesDir, entry.source);
2213
- if (!existsSync12(source)) continue;
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 (existsSync12(join11(projectDir, LEGACY_ANCHOR_FILE))) {
2292
+ if (existsSync13(join12(projectDir, LEGACY_ANCHOR_FILE))) {
2239
2293
  report.legacyAnchor = LEGACY_ANCHOR_FILE;
2240
2294
  }
2241
- const templateMd = join11(templatesDir, "CLAUDE.md");
2242
- if (!existsSync12(templateMd)) return;
2243
- const anchor = join11(projectDir, HARNESS_ANCHOR_FILE);
2244
- const existed = existsSync12(anchor);
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 = join11(projectDir, "CLAUDE.md");
2256
- const existing = existsSync12(target) ? readFileSync11(target, "utf8") : null;
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(readFileSync11(anchor, "utf8"))
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 (!existsSync12(target) || !existsSync12(source)) return { updated: 0, backedUp: [] };
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 = join11(target, file);
2326
- const sourceFile = join11(source, file);
2327
- if (!existsSync12(sourceFile)) continue;
2328
- const next = readFileSync11(sourceFile, "utf8");
2329
- const current = readFileSync11(targetFile, "utf8");
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 (!existsSync12(targetDir) || !existsSync12(sourceDir))
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 = join11(targetDir, skill.name);
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 (!existsSync12(targetSkill)) continue;
2355
- for (const rel of listFilesRecursive(join11(sourceDir, skill.name))) {
2356
- const targetFile = join11(targetSkill, rel);
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 = readFileSync11(join11(sourceDir, skill.name, rel), "utf8");
2363
- if (!existsSync12(targetFile)) {
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 = readFileSync11(targetFile, "utf8");
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 (!existsSync12(target) || !existsSync12(source)) return [];
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 = join11(source, file);
2420
- if (!existsSync12(sourceFile)) {
2421
- const targetFile = join11(target, file);
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 = readFileSync11(targetFile, "utf8");
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 = join11(projectDir, ".mcp-allowlist");
2436
- if (!existsSync12(target)) return null;
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(readFileSync11(settingsPath, "utf8"));
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 = existsSync12(join11(claudeDir, relPath));
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 = join12(harnessRoot, "templates");
2503
- if (!existsSync13(templatesDir)) {
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 = join12(projectDir, ".claude");
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" && !existsSync13(claudeDir) && (previousLog === null || claudeWasSelected)) {
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(join12(projectDir, asset.target));
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 = join12(projectDir, ".claude/settings.json");
2614
- if (!existsSync13(settingsPath)) return [];
2615
- return cleanStaleHookRefs(settingsPath, join12(projectDir, ".claude"));
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 = join12(templatesDir, entry.source);
2685
- if (!existsSync13(source)) {
2744
+ const source = join13(templatesDir, entry.source);
2745
+ if (!existsSync14(source)) {
2686
2746
  result.skipped += 1;
2687
2747
  continue;
2688
2748
  }
2689
- copyFile(source, join12(projectDir, entry.target));
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 (!existsSync13(target)) return null;
2702
- const current = readFileSync12(target, "utf-8");
2703
- if (current === readFileSync12(source, "utf-8")) return null;
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 = join12(projectDir, entry.target);
2776
+ const target = join13(projectDir, entry.target);
2717
2777
  if (isBaselineExcluded(entry.target, baselineExcluded)) {
2718
2778
  result.excluded.push(entry.target);
2719
- if (existsSync13(target)) result.excludedOnDisk.push(entry.target);
2779
+ if (existsSync14(target)) result.excludedOnDisk.push(entry.target);
2720
2780
  continue;
2721
2781
  }
2722
- const source = join12(templatesDir, entry.source);
2723
- if (!existsSync13(source)) {
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, readFileSync12(source, "utf-8"));
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 = join12(projectDir, ".claude/hooks");
2759
- if (existsSync13(hookDir)) {
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(projectDir, manifestSpec.tracks);
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 = join12(projectDir, HARNESS_ANCHOR_FILE);
2770
- if (!existsSync13(anchor)) return null;
2771
- return { path: HARNESS_ANCHOR_FILE, sha256: hashContent(readFileSync12(anchor, "utf-8")) };
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, join12(ctx.harnessRoot, "templates"));
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 = join12(projectDir, ".mcp.json");
2847
- const created = !existsSync13(mcpPath);
2910
+ const mcpPath = join13(projectDir, ".mcp.json");
2911
+ const created = !existsSync14(mcpPath);
2848
2912
  const composed = composeMcpJson({
2849
- templateMcpPath: join12(harnessRoot, "templates/mcp.json"),
2850
- trackMapPath: join12(harnessRoot, "templates/track-mcp-map.tsv"),
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 = join12(projectDir, ".claude/.installed-tracks");
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 = join12(projectDir, "CLAUDE.md");
2911
- const existing = existsSync13(target) ? readFileSync12(target, "utf-8") : null;
2912
- const content = upsertHarnessImport(existing, { projectName: basename5(projectDir), tracks });
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
- chmodSync2(file, 493);
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>/SKILL.md",
3089
- `${report.codex.skillFiles.length} skills`
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.length} dev-method skills (codex\xB7antigravity \uC640 \uAC19\uC740 \uC790\uB9AC)`
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>/SKILL.md",
3130
- `${report.antigravity.skillFiles.length} skills`
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
- log(
3370
- assetRow(
3371
- "skip",
3372
- "external skills",
3373
- `${baseline.updateMode.externalSkillsNotInCatalog.join(", ")} \xB7 \uCE74\uD0C8\uB85C\uADF8\uC5D0 \uC5C6\uC5B4 \uAC31\uC2E0 \uB300\uC0C1\uC774 \uC544\uB2C8\uB2E4`
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
- "spec-scaling \xB7 deep-research \xB7 ui-visual-review \xB7 eval-harness (modified)",
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 existsSync14, readFileSync as readFileSync13 } from "fs";
3763
- import { join as join13, resolve as resolve3 } from "path";
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 = join13(projectDir, rootMd.path);
3827
- const modified = existsSync14(path) && hashContent(readFileSync13(path, "utf8")) !== rootMd.sha256;
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) => existsSync14(join13(projectDir, f.path)));
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 existsSync15,
3968
+ existsSync as existsSync16,
3854
3969
  lstatSync as lstatSync2,
3855
3970
  readdirSync as readdirSync5,
3856
- readFileSync as readFileSync14,
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 join14, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
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(join14(projectDir, INSTALL_LOG_DIR));
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) => existsSync15(join14(projectDir, f.path)));
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(join14(projectDir, log.templates.claudeDir));
5253
- if (log.templates.codexDir) rm(join14(projectDir, log.templates.codexDir));
5254
- if (log.templates.opencodeDir) rm(join14(projectDir, log.templates.opencodeDir));
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(join14(projectDir, rootMd.path));
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 = join14(projectDir, path);
5270
- if (!existsSync15(abs)) continue;
5384
+ const abs = join15(projectDir, path);
5385
+ if (!existsSync16(abs)) continue;
5271
5386
  if (!lstatSync2(abs).isFile()) continue;
5272
- if (hashContent(readFileSync14(abs, "utf8")) !== sha256) {
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(join14(projectDir, path)));
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(join14(projectDir, "CLAUDE.md"), stripped, "utf8");
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 = join14(projectDir, path);
5312
- if (!existsSync15(abs)) continue;
5313
- if (hashContent(readFileSync14(abs, "utf8")) !== sha256) {
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 = join14(projectDir, "CLAUDE.md");
5340
- if (!existsSync15(path)) return null;
5454
+ const path = join15(projectDir, "CLAUDE.md");
5455
+ if (!existsSync16(path)) return null;
5341
5456
  try {
5342
- return readFileSync14(path, "utf8");
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 = join14(projectDir, rootMd.path);
5351
- if (!existsSync15(path)) return false;
5352
- return hashContent(readFileSync14(path, "utf8")) !== rootMd.sha256;
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 (existsSync15(path)) {
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 existsSync16, readFileSync as readFileSync15 } from "fs";
5413
- import { join as join15 } from "path";
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 = join15(projectDir, ".claude");
5424
- const hasClaudeDir = existsSync16(claudeDir);
5538
+ const claudeDir = join16(projectDir, ".claude");
5539
+ const hasClaudeDir = existsSync17(claudeDir);
5425
5540
  if (hasClaudeDir) {
5426
- const metaPath = join15(projectDir, META_FILE);
5427
- if (existsSync16(metaPath)) {
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 = readFileSync15(path, "utf8");
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 = join15(projectDir, ".claude/rules");
5461
- if (!existsSync16(rulesDir)) {
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 (existsSync16(join15(rulesDir, sig.rule))) {
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"] },