@uzysjung/agent-harness 26.150.0 → 26.152.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 (69) hide show
  1. package/README.ko.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/{chunk-NKBUDHPC.js → chunk-EQDC2AAU.js} +125 -69
  4. package/dist/chunk-EQDC2AAU.js.map +1 -0
  5. package/dist/index.js +269 -284
  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/reviewer.md +4 -5
  11. package/templates/antigravity/AGENTS.md.template +3 -23
  12. package/templates/codex/AGENTS.md.template +5 -56
  13. package/templates/hooks/protect-files.sh +4 -0
  14. package/templates/opencode/AGENTS.md.template +4 -52
  15. package/templates/opencode/opencode.json.template +0 -8
  16. package/templates/rules/change-management.md +2 -4
  17. package/templates/rules/cli-development.md +2 -4
  18. package/templates/rules/doc-governance.md +3 -1
  19. package/templates/rules/git-policy.md +1 -1
  20. package/templates/rules/ship-checklist.md +3 -3
  21. package/templates/rules/test-policy.md +3 -8
  22. package/templates/settings.json +1 -16
  23. package/templates/skills/audit-harness-fit/README.md +113 -0
  24. package/templates/skills/audit-harness-fit/SKILL.md +64 -433
  25. package/templates/skills/audit-harness-fit/evals/scenarios.yaml +222 -0
  26. package/templates/skills/audit-harness-fit/references/apply.md +66 -0
  27. package/templates/skills/audit-harness-fit/references/audit.md +160 -0
  28. package/templates/skills/audit-harness-fit/references/populate.md +74 -0
  29. package/templates/skills/audit-harness-fit/references/verification.md +123 -0
  30. package/templates/skills/audit-service-gaps/SKILL.md +0 -2
  31. package/templates/skills/compaction-handoff/SKILL.md +2 -2
  32. package/templates/skills/model-orchestration/SKILL.md +13 -24
  33. package/templates/skills/natural-korean/SKILL.md +45 -0
  34. package/templates/skills/north-star/references/roadmap-method.md +2 -2
  35. package/templates/skills/{task-brief → objective-brief}/SKILL.md +17 -16
  36. package/templates/skills/recurrence-prevention/SKILL.md +16 -16
  37. package/templates/track-mcp-map.tsv +1 -1
  38. package/dist/chunk-NKBUDHPC.js.map +0 -1
  39. package/templates/agents/build-error-resolver.md +0 -111
  40. package/templates/agents/code-reviewer.md +0 -237
  41. package/templates/agents/plan-checker.md +0 -116
  42. package/templates/agents/security-reviewer.md +0 -108
  43. package/templates/agents/silent-failure-hunter.md +0 -50
  44. package/templates/hooks/task-brief-nudge.sh +0 -57
  45. package/templates/skills/agent-introspection-debugging/SKILL.md +0 -153
  46. package/templates/skills/audit-harness-fit/references/official-criteria.md +0 -367
  47. package/templates/skills/continuous-learning-v2/SKILL.md +0 -361
  48. package/templates/skills/continuous-learning-v2/agents/observer-loop.sh +0 -362
  49. package/templates/skills/continuous-learning-v2/agents/observer.md +0 -189
  50. package/templates/skills/continuous-learning-v2/agents/session-guardian.sh +0 -150
  51. package/templates/skills/continuous-learning-v2/agents/start-observer.sh +0 -252
  52. package/templates/skills/continuous-learning-v2/config.json +0 -8
  53. package/templates/skills/continuous-learning-v2/hooks/observe.sh +0 -585
  54. package/templates/skills/continuous-learning-v2/scripts/detect-project.sh +0 -322
  55. package/templates/skills/continuous-learning-v2/scripts/instinct-cli.py +0 -1956
  56. package/templates/skills/continuous-learning-v2/scripts/lib/homunculus-dir.sh +0 -31
  57. package/templates/skills/continuous-learning-v2/scripts/migrate-homunculus.sh +0 -68
  58. package/templates/skills/continuous-learning-v2/scripts/test_parse_instinct.py +0 -1420
  59. package/templates/skills/deep-research/SKILL.md +0 -179
  60. package/templates/skills/deep-research/agents/openai.yaml +0 -7
  61. package/templates/skills/eval-harness/SKILL.md +0 -306
  62. package/templates/skills/eval-harness/agents/openai.yaml +0 -7
  63. package/templates/skills/humanize-korean/SKILL.md +0 -228
  64. package/templates/skills/spec-scaling/SKILL.md +0 -89
  65. package/templates/skills/strategic-compact/SKILL.md +0 -145
  66. package/templates/skills/strategic-compact/suggest-compact.sh +0 -54
  67. package/templates/skills/verification-loop/SKILL.md +0 -250
  68. package/templates/skills/verification-loop/agents/openai.yaml +0 -7
  69. package/templates/skills/verification-loop/references/tracks.md +0 -49
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,10 @@ import {
12
13
  INSTALL_LOG_DIR,
13
14
  INTERNAL_BUNDLED_SKILL_IDS,
14
15
  POLICY_DIRS,
16
+ RENAMED_SKILL_IDS,
17
+ RETIRED_AGENTS,
18
+ RETIRED_AGENT_IDS,
19
+ RETIRED_SKILL_IDS,
15
20
  TRACKS,
16
21
  __commonJS,
17
22
  __toESM,
@@ -56,8 +61,9 @@ import {
56
61
  stripHarnessImport,
57
62
  summarizeContextCost,
58
63
  upsertHarnessImport,
64
+ withContinuousSkillsNote,
59
65
  writeInstallLog
60
- } from "./chunk-NKBUDHPC.js";
66
+ } from "./chunk-EQDC2AAU.js";
61
67
 
62
68
  // node_modules/sisteransi/src/index.js
63
69
  var require_src = __commonJS({
@@ -742,7 +748,7 @@ var cac = (name = "") => new CAC(name);
742
748
  // package.json
743
749
  var package_default = {
744
750
  name: "@uzysjung/agent-harness",
745
- version: "26.150.0",
751
+ version: "26.152.0",
746
752
  description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
747
753
  type: "module",
748
754
  publishConfig: {
@@ -980,12 +986,11 @@ function padDisplay(s, width) {
980
986
  // src/installer.ts
981
987
  init_esm_shims();
982
988
  import {
983
- chmodSync as chmodSync2,
989
+ chmodSync as chmodSync3,
984
990
  existsSync as existsSync13,
985
991
  mkdirSync as mkdirSync4,
986
992
  readdirSync as readdirSync4,
987
993
  readFileSync as readFileSync12,
988
- unlinkSync as unlinkSync3,
989
994
  writeFileSync as writeFileSync6
990
995
  } from "fs";
991
996
  import { basename as basename5, dirname as dirname4, join as join12, resolve } from "path";
@@ -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 }
@@ -2052,41 +2089,6 @@ function writeMcpJson(path, mcp) {
2052
2089
  `);
2053
2090
  }
2054
2091
 
2055
- // src/superseded.ts
2056
- init_esm_shims();
2057
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
2058
- import { join as join10 } from "path";
2059
- function sameTracks(a, b2) {
2060
- if (a.length !== b2.length) return false;
2061
- const sa = [...a].sort();
2062
- const sb = [...b2].sort();
2063
- return sa.every((t, i) => t === sb[i]);
2064
- }
2065
- function policyKey(claudeRelative) {
2066
- return POLICY_DIRS.some(
2067
- ({ dir, ext }) => claudeRelative.startsWith(`${dir}/`) && claudeRelative.endsWith(ext)
2068
- ) ? claudeRelative : null;
2069
- }
2070
- function findSuperseded(projectDir, spec, previous) {
2071
- if (!previous) return [];
2072
- if (!sameTracks(previous.spec.tracks, spec.tracks)) return [];
2073
- const baseline = new Map((previous.policyFiles ?? []).map((f) => [f.path, f.sha256]));
2074
- if (baseline.size === 0) return [];
2075
- const out = [];
2076
- for (const entry of buildManifest(spec)) {
2077
- if (entry.applies(spec)) continue;
2078
- const id = classifyBaselineTarget(entry.target)?.id;
2079
- if (!id) continue;
2080
- const key = policyKey(entry.target.slice(".claude/".length));
2081
- 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;
2085
- out.push({ target: entry.target, id, source: entry.source });
2086
- }
2087
- return out;
2088
- }
2089
-
2090
2092
  // src/update-mode.ts
2091
2093
  init_esm_shims();
2092
2094
  import {
@@ -2118,6 +2120,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
2118
2120
  claudeMdUpdated: false,
2119
2121
  anchorCreated: false,
2120
2122
  rootImportAdded: false,
2123
+ rootBlockRefreshed: false,
2121
2124
  legacyAnchor: null,
2122
2125
  skillsBackedUp: [],
2123
2126
  skillsSkippedLinks: [],
@@ -2132,6 +2135,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
2132
2135
  installedNew: [],
2133
2136
  restored: [],
2134
2137
  needsReinstall: [],
2138
+ retiredAgents: [],
2135
2139
  mcpAllowlistRetired: null
2136
2140
  };
2137
2141
  const fresh = installNewAssets(projectDir, templatesDir);
@@ -2170,6 +2174,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
2170
2174
  report.staleHookRefs = cleanStaleHookRefs(settingsPath, claudeDir);
2171
2175
  }
2172
2176
  report.mcpAllowlistRetired = retireMcpAllowlist(projectDir);
2177
+ report.retiredAgents = staleAgentFiles(claudeDir);
2173
2178
  const external = refreshExternalCli(projectDir, harnessRoot);
2174
2179
  report.externalUpdated = external.externalUpdated;
2175
2180
  report.externalBackedUp = external.externalBackedUp;
@@ -2186,10 +2191,24 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}) {
2186
2191
  const skillRefresh = (deps.refreshSkills ?? refreshExternalSkills)(projectDir);
2187
2192
  report.externalSkillsRefreshed = skillRefresh.refreshed;
2188
2193
  report.externalSkillsFailed = skillRefresh.failed;
2189
- report.externalSkillsNotInCatalog = skillRefresh.notInCatalog;
2194
+ report.externalSkillsNotInCatalog = [
2195
+ ...skillRefresh.notInCatalog,
2196
+ ...staleSkillDirs(claudeDir).filter((id) => !skillRefresh.notInCatalog.includes(id))
2197
+ ];
2190
2198
  report.externalSkillsUnknown = skillRefresh.unknown;
2191
2199
  return report;
2192
2200
  }
2201
+ function staleSkillDirs(claudeDir) {
2202
+ const dir = join11(claudeDir, "skills");
2203
+ if (!existsSync12(dir)) return [];
2204
+ const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
2205
+ return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && stale(e2.name)).map((e2) => e2.name);
2206
+ }
2207
+ function staleAgentFiles(claudeDir) {
2208
+ const dir = join11(claudeDir, "agents");
2209
+ if (!existsSync12(dir)) return [];
2210
+ return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename4(e2.name, ".md")).filter((id) => RETIRED_AGENT_IDS.includes(id));
2211
+ }
2193
2212
  function installNewAssets(projectDir, templatesDir) {
2194
2213
  const installed = [];
2195
2214
  const restored = [];
@@ -2245,6 +2264,7 @@ function syncHarnessAnchor(projectDir, templatesDir, report) {
2245
2264
  copyFileSync(templateMd, anchor);
2246
2265
  if (existed) {
2247
2266
  report.claudeMdUpdated = true;
2267
+ report.rootBlockRefreshed = upsertRootImport(projectDir);
2248
2268
  return;
2249
2269
  }
2250
2270
  report.anchorCreated = true;
@@ -2256,7 +2276,12 @@ function upsertRootImport(projectDir) {
2256
2276
  const existing = existsSync12(target) ? readFileSync11(target, "utf8") : null;
2257
2277
  const next = upsertHarnessImport(existing, {
2258
2278
  projectName: basename4(projectDir),
2259
- tracks: installedTracks(projectDir)
2279
+ tracks: installedTracks(projectDir),
2280
+ // ADR-085 — update 는 선택 목록을 인자로 받지 않는다. `syncSkills` 와 같은 판정을 쓴다:
2281
+ // `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
2282
+ continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
2283
+ (id) => existsSync12(join11(projectDir, ".claude/skills", id))
2284
+ )
2260
2285
  });
2261
2286
  if (next === existing) return false;
2262
2287
  writeFileSync5(target, next);
@@ -2518,7 +2543,6 @@ function runInstall(ctx) {
2518
2543
  const manifestSpec = buildManifestSpec(spec);
2519
2544
  const policyBase = new Map((previousLog?.policyFiles ?? []).map((f) => [f.path, f.sha256]));
2520
2545
  const baselineExcluded = new Set(spec.baselineExclude ?? []);
2521
- const supersededFound = findSuperseded(projectDir, manifestSpec, previousLog);
2522
2546
  const base = spec.cli.includes("claude") ? installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBase, baselineExcluded) : (
2523
2547
  // claude 미선택이어도 CLI 중립 자산은 깔린다. manifest 전체가 `.claude/` baseline 안에서만
2524
2548
  // 돌던 탓에 이 자산들이 claude 설치에만 도달했는데, **배포 룰 본문이 이 스크립트들을
@@ -2574,10 +2598,7 @@ function runInstall(ctx) {
2574
2598
  baselineForeignOwned: [
2575
2599
  ...base.foreignOwned,
2576
2600
  ...externalForeignOwned.filter((f) => !base.foreignOwned.includes(f))
2577
- ],
2578
- // 밀려난 자산 정리는 **manifest 복사가 끝난 뒤**에 한다. 앞에서 지우면 이번 설치가 다시
2579
- // 깔 수도 있는 파일을 지웠다 되돌리는 셈이라, 무엇이 최종 상태인지 보고가 흐려진다.
2580
- superseded: applySupersededCleanup(spec, projectDir, supersededFound)
2601
+ ]
2581
2602
  };
2582
2603
  ctx.onProgress?.({ type: "baseline-complete", baseline });
2583
2604
  const external = runExternalPhase(ctx);
@@ -2593,21 +2614,6 @@ function runInstall(ctx) {
2593
2614
  );
2594
2615
  return { ...baseline, external, staleHookRefs };
2595
2616
  }
2596
- function applySupersededCleanup(spec, projectDir, found) {
2597
- if (found.length === 0) return { removed: [], kept: [] };
2598
- if (!spec.cleanSuperseded) return { removed: [], kept: found.map((f) => f.target) };
2599
- const removed = [];
2600
- const kept = [];
2601
- for (const asset of found) {
2602
- try {
2603
- unlinkSync3(join12(projectDir, asset.target));
2604
- removed.push(asset.target);
2605
- } catch {
2606
- kept.push(asset.target);
2607
- }
2608
- }
2609
- return { removed, kept };
2610
- }
2611
2617
  function healStaleHookRefs(spec, projectDir) {
2612
2618
  if (!spec.cli.includes("claude")) return [];
2613
2619
  const settingsPath = join12(projectDir, ".claude/settings.json");
@@ -2628,9 +2634,6 @@ function runUpdateInstall(ctx, templatesDir, backupPath) {
2628
2634
  baselineExcluded: [],
2629
2635
  baselineExcludedOnDisk: [],
2630
2636
  baselineForeignOwned: [],
2631
- // update 는 밀려난 자산을 다루지 않는다 — 그 판정은 사용자가 옵션을 고르는 설치 경로의
2632
- // 것이고, update 는 무엇을 골랐는지 다시 묻지 않는다.
2633
- superseded: { removed: [], kept: [] },
2634
2637
  backup: backupPath,
2635
2638
  installedTracks: [...ctx.spec.tracks].sort(),
2636
2639
  mcpServers: [],
@@ -2760,7 +2763,11 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBas
2760
2763
  chmodHooksSync(hookDir);
2761
2764
  }
2762
2765
  writeInstalledTracks(projectDir, manifestSpec.tracks);
2763
- const rootClaudeMd = writeRootClaudeMd(projectDir, manifestSpec.tracks);
2766
+ const rootClaudeMd = writeRootClaudeMd(
2767
+ projectDir,
2768
+ manifestSpec.tracks,
2769
+ manifestSpec.selectedInternalSkills ?? []
2770
+ );
2764
2771
  result.rootClaudeMd = { tracks: manifestSpec.tracks, created: rootClaudeMd.created };
2765
2772
  result.rootClaudeMdLog = harnessAnchorLog(projectDir);
2766
2773
  return result;
@@ -2906,10 +2913,14 @@ function writeInstalledTracks(projectDir, tracks) {
2906
2913
  writeFileSync6(path, `${sorted}
2907
2914
  `);
2908
2915
  }
2909
- function writeRootClaudeMd(projectDir, tracks) {
2916
+ function writeRootClaudeMd(projectDir, tracks, continuousSkills) {
2910
2917
  const target = join12(projectDir, "CLAUDE.md");
2911
2918
  const existing = existsSync13(target) ? readFileSync12(target, "utf-8") : null;
2912
- const content = upsertHarnessImport(existing, { projectName: basename5(projectDir), tracks });
2919
+ const content = upsertHarnessImport(existing, {
2920
+ projectName: basename5(projectDir),
2921
+ tracks,
2922
+ continuousSkills
2923
+ });
2913
2924
  if (content !== existing) {
2914
2925
  writeFileSync6(target, content);
2915
2926
  }
@@ -2918,7 +2929,7 @@ function writeRootClaudeMd(projectDir, tracks) {
2918
2929
  function chmodHooksSync(hookDir) {
2919
2930
  for (const file of listHookFiles(hookDir)) {
2920
2931
  try {
2921
- chmodSync2(file, 493);
2932
+ chmodSync3(file, 493);
2922
2933
  } catch {
2923
2934
  }
2924
2935
  }
@@ -3085,8 +3096,8 @@ function renderCliArtifacts(log, spec, report) {
3085
3096
  log(
3086
3097
  assetRow(
3087
3098
  "success",
3088
- ".agents/skills/<id>/SKILL.md",
3089
- `${report.codex.skillFiles.length} skills`
3099
+ ".agents/skills/<id>/",
3100
+ `${countSkillDirs(report.codex.skillFiles)} skills`
3090
3101
  )
3091
3102
  );
3092
3103
  }
@@ -3104,7 +3115,7 @@ function renderCliArtifacts(log, spec, report) {
3104
3115
  assetRow(
3105
3116
  "success",
3106
3117
  ".agents/skills/",
3107
- `${report.opencode.skillFiles.length} dev-method skills (codex\xB7antigravity \uC640 \uAC19\uC740 \uC790\uB9AC)`
3118
+ `${countSkillDirs(report.opencode.skillFiles)} dev-method skills (codex\xB7antigravity \uC640 \uAC19\uC740 \uC790\uB9AC)`
3108
3119
  )
3109
3120
  );
3110
3121
  }
@@ -3126,8 +3137,8 @@ function renderCliArtifacts(log, spec, report) {
3126
3137
  log(
3127
3138
  assetRow(
3128
3139
  "success",
3129
- ".agents/skills/<id>/SKILL.md",
3130
- `${report.antigravity.skillFiles.length} skills`
3140
+ ".agents/skills/<id>/",
3141
+ `${countSkillDirs(report.antigravity.skillFiles)} skills`
3131
3142
  )
3132
3143
  );
3133
3144
  }
@@ -3193,15 +3204,25 @@ function renderFinalSummary(log, spec, report, fromWizard) {
3193
3204
  log(infoRow("NEXT", `Open ${c.bold(label)} \u2014 installed rules & skills are now active`));
3194
3205
  const scaffoldFiles = scaffoldFilesForCli(spec.cli);
3195
3206
  if (scaffoldFiles.length > 0) {
3207
+ const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
3208
+ const populateHint = auditInstalled ? `, or ask the ${c.bold("audit-harness-fit")} skill to fill it from repository evidence` : "";
3196
3209
  log(
3197
3210
  infoRow(
3198
3211
  "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`
3212
+ `${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
3213
  )
3201
3214
  );
3202
3215
  }
3203
3216
  log("");
3204
3217
  }
3218
+ function countSkillDirs(skillFiles) {
3219
+ const ids = /* @__PURE__ */ new Set();
3220
+ for (const f of skillFiles) {
3221
+ const m = /[\\/]\.agents[\\/]skills[\\/]([^\\/]+)[\\/]/.exec(f);
3222
+ if (m?.[1] !== void 0) ids.add(m[1]);
3223
+ }
3224
+ return ids.size;
3225
+ }
3205
3226
  function scaffoldFilesForCli(cli2) {
3206
3227
  const files = [];
3207
3228
  if (cli2.includes("claude")) {
@@ -3268,6 +3289,15 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3268
3289
  if (baseline.updateMode.rootImportAdded) {
3269
3290
  log(assetRow("success", "CLAUDE.md", `${HARNESS_IMPORT_LINE} import added`));
3270
3291
  }
3292
+ if (baseline.updateMode.rootBlockRefreshed) {
3293
+ log(
3294
+ assetRow(
3295
+ "success",
3296
+ "CLAUDE.md",
3297
+ "harness block refreshed (skills that apply continuously)"
3298
+ )
3299
+ );
3300
+ }
3271
3301
  if (baseline.updateMode.legacyAnchor) {
3272
3302
  log(
3273
3303
  assetRow(
@@ -3313,6 +3343,16 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3313
3343
  )
3314
3344
  );
3315
3345
  }
3346
+ for (const id of baseline.updateMode.retiredAgents) {
3347
+ const instead = RETIRED_AGENTS.find((a) => a.id === id)?.instead;
3348
+ log(
3349
+ assetRow(
3350
+ "skip",
3351
+ "agents",
3352
+ `${id} \xB7 \uC774 \uB9B4\uB9AC\uC988\uC5D0\uC11C \uC740\uD1F4 \u2014 .claude/agents/${id}.md \uB97C \uC9C0\uC6CC\uB3C4 \uB41C\uB2E4` + (instead === void 0 ? "" : ` \xB7 ${instead}`)
3353
+ )
3354
+ );
3355
+ }
3316
3356
  if (baseline.updateMode.policyBackedUp.length > 0) {
3317
3357
  log(
3318
3358
  assetRow(
@@ -3366,13 +3406,37 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3366
3406
  log(assetRow("skip", "external skills", `${meta} \xB7 \uB2E4\uC74C update \uC5D0\uC11C \uB2E4\uC2DC \uC2DC\uB3C4\uD55C\uB2E4`));
3367
3407
  }
3368
3408
  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
- );
3409
+ const ids = baseline.updateMode.externalSkillsNotInCatalog;
3410
+ for (const id of ids) {
3411
+ const renamedTo = RENAMED_SKILL_IDS[id];
3412
+ if (renamedTo) {
3413
+ log(
3414
+ assetRow(
3415
+ "skip",
3416
+ "skills",
3417
+ `${id} \uB294 ${renamedTo} \uAC00 \uB410\uB2E4 \xB7 update \uB4A4 .claude/skills/${id} \uB97C \uC9C0\uC6B0\uACE0 ${renamedTo} \uB97C \uC120\uD0DD\uD574 \uBC1B\uB294\uB2E4`
3418
+ )
3419
+ );
3420
+ } else if (RETIRED_SKILL_IDS.includes(id)) {
3421
+ log(
3422
+ assetRow(
3423
+ "skip",
3424
+ "skills",
3425
+ `${id} \xB7 \uC774 \uB9B4\uB9AC\uC988\uC5D0\uC11C \uC740\uD1F4 \u2014 .claude/skills/${id} \uB97C \uC9C0\uC6CC\uB3C4 \uB41C\uB2E4`
3426
+ )
3427
+ );
3428
+ }
3429
+ }
3430
+ const unknown = ids.filter((id) => !RENAMED_SKILL_IDS[id] && !RETIRED_SKILL_IDS.includes(id));
3431
+ if (unknown.length > 0) {
3432
+ log(
3433
+ assetRow(
3434
+ "skip",
3435
+ "external skills",
3436
+ `${unknown.join(", ")} \xB7 \uCE74\uD0C8\uB85C\uADF8\uC5D0 \uC5C6\uC5B4 \uAC31\uC2E0 \uB300\uC0C1\uC774 \uC544\uB2C8\uB2E4`
3437
+ )
3438
+ );
3439
+ }
3376
3440
  }
3377
3441
  if (baseline.updateMode.externalSkillsUnknown) {
3378
3442
  log(
@@ -3412,17 +3476,12 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3412
3476
  phase1Row(
3413
3477
  "agents",
3414
3478
  cats.agents.length,
3415
- "SOD reviewer (opus, independent verifier) + 3 base",
3479
+ "independent verifier \xB7 implementation lane \xB7 domain lanes (track)",
3416
3480
  cats.agents
3417
3481
  );
3418
3482
  }
3419
3483
  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
- );
3484
+ phase1Row("hooks", cats.hooks.length, "session-start \xB7 protect-files", cats.hooks);
3426
3485
  }
3427
3486
  if (cats.commands > 0) {
3428
3487
  phase1Row("commands", cats.commands, "slash commands");
@@ -3431,7 +3490,7 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3431
3490
  phase1Row(
3432
3491
  "skills",
3433
3492
  cats.skills.length,
3434
- "spec-scaling \xB7 deep-research \xB7 ui-visual-review \xB7 eval-harness (modified)",
3493
+ "bundled methodology skills (track \xB7 opt-in)",
3435
3494
  cats.skills
3436
3495
  );
3437
3496
  }
@@ -3462,24 +3521,6 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3462
3521
  )
3463
3522
  );
3464
3523
  }
3465
- if (baseline.superseded.removed.length > 0) {
3466
- log(
3467
- assetRow(
3468
- "success",
3469
- "superseded \u2014 cleaned",
3470
- `${baseline.superseded.removed.length} \u2014 ${baseline.superseded.removed.map((t) => t.replace(/^\.claude\//, "")).join(", ")} \xB7 \uB418\uB3CC\uB9AC\uB824\uBA74 \uADF8 \uC790\uC0B0 \uC5C6\uC774 \uC7AC\uC124\uCE58`
3471
- )
3472
- );
3473
- }
3474
- if (baseline.superseded.kept.length > 0) {
3475
- log(
3476
- assetRow(
3477
- "skip",
3478
- "superseded \u2014 kept",
3479
- `${baseline.superseded.kept.length} \u2014 ${baseline.superseded.kept.map((t) => t.replace(/^\.claude\//, "")).join(", ")} \xB7 \uC774\uBC88 \uC120\uD0DD\uC774 \uB300\uCCB4\uD588\uC9C0\uB9CC \uC9C0\uC6B0\uC9C0 \uC54A\uC558\uB2E4 (\uC81C\uAC70: agent-harness uninstall)`
3480
- )
3481
- );
3482
- }
3483
3524
  const TEMPLATES_COL = 28;
3484
3525
  if (baseline.rootClaudeMd) {
3485
3526
  const n = baseline.rootClaudeMd.tracks.length;
@@ -5593,7 +5634,7 @@ var INSTALL_TARGET_PAGES = [
5593
5634
  // 2026-08-16 — 트랙이 고르는 자산을 **맨 앞에서** 전부 보여준다. 그전까지 이 항목들은 화면에
5594
5635
  // 한 번도 안 나온 채 깔렸고, 사용자는 설치가 끝난 뒤 요약에서 처음 봤다.
5595
5636
  //
5596
- // 2페이지로 나눈 근거는 실측이다: 최대 트랙(`full`)이 32항목(rules 6 · agents 9 · hooks 3 ·
5637
+ // 2페이지로 나눈 근거는 실측이다: 최대 트랙(`full`)이 32항목(당시 실측 — rules 6 · agents 9 · hooks 3 ·
5597
5638
  // skills 14)이라 clack groupMultiselect 의 "페이지당 ≤ ~30행" 제약을 한 페이지로는 못 지킨다.
5598
5639
  // 가르는 축은 역할이다 — 정책·가드가 한쪽, 실행 능력이 다른 쪽.
5599
5640
  { label: "Track baseline \u2014 Rules & Hooks (\uC815\uCC45\xB7\uAC00\uB4DC)", cats: [], baseline: ["rules", "hooks"] },
@@ -5775,23 +5816,6 @@ Proceed?`,
5775
5816
  });
5776
5817
  return q(result) ? null : result;
5777
5818
  },
5778
- confirmSupersededCleanup: async (items, context) => {
5779
- const names = items.map((i) => i.target.replace(/^\.claude\//, "")).join("\n ");
5780
- const result = await ue({
5781
- message: [
5782
- `${context.by.join(" \xB7 ")} \uC744(\uB97C) \uACE8\uB77C\uC11C \uC544\uB798 ${items.length}\uAC1C\uAC00 \uC124\uCE58 \uB300\uC0C1\uC5D0\uC11C \uBE60\uC84C\uC2B5\uB2C8\uB2E4.`,
5783
- "\uADF8\uB7F0\uB370 \uC774\uC804 \uC124\uCE58\uBCF8\uC774 \uB514\uC2A4\uD06C\uC5D0 \uADF8\uB300\uB85C \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4:",
5784
- ` ${names}`,
5785
- "",
5786
- `\uADF8\uB300\uB85C \uB450\uBA74 \uAC19\uC740 \uC77C\uC744 \uD558\uB294 \uC5D0\uC774\uC804\uD2B8\uAC00 \uB450 \uBC8C\uC774\uACE0, \uB9E4 \uC138\uC158 ~${context.tokens} tok \uB97C \uACC4\uC18D \uBB45\uB2C8\uB2E4.`,
5787
- "(\uD558\uB124\uC2A4\uAC00 \uAE54\uC558\uACE0 \uC218\uC815\uB418\uC9C0 \uC54A\uC740 \uD30C\uC77C\uB9CC \uB300\uC0C1\uC785\uB2C8\uB2E4 \u2014 \uC9C1\uC811 \uACE0\uCE5C \uD30C\uC77C\uC740 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4)",
5788
- "",
5789
- "\uC815\uB9AC\uD560\uAE4C\uC694?"
5790
- ].join("\n"),
5791
- initialValue: true
5792
- });
5793
- return q(result) ? null : result;
5794
- },
5795
5819
  selectInstallTargets: async (initialChecked, step, recap) => {
5796
5820
  const pages = INSTALL_TARGET_PAGES;
5797
5821
  const baselineTargets = listBaselineTargets({ tracks: recap?.tracks ?? [] });
@@ -6057,50 +6081,11 @@ ${summary}`
6057
6081
  ...userOverride ? { userOverride } : {},
6058
6082
  ...baselineExclude.length > 0 ? { baselineExclude } : {}
6059
6083
  };
6060
- const superseded = findSuperseded(
6061
- projectDir,
6062
- buildManifestSpec(spec),
6063
- readInstallLog(projectDir)
6064
- );
6065
- let cleanSuperseded = false;
6066
- if (superseded.length > 0) {
6067
- const cause = supersededCause(projectDir, spec, readInstallLog(projectDir), superseded);
6068
- const answer = await prompts.confirmSupersededCleanup(superseded, {
6069
- by: cause.length > 0 ? cause : ["\uC774\uBC88 \uC120\uD0DD"],
6070
- tokens: supersededTokens(superseded)
6071
- });
6072
- if (answer === null) {
6073
- step = "confirm";
6074
- continue;
6075
- }
6076
- cleanSuperseded = answer;
6077
- }
6078
6084
  prompts.outro(stepLabel(WIZARD.INSTALL, "Installing..."));
6079
- return {
6080
- ok: true,
6081
- mode,
6082
- spec: { ...spec, ...cleanSuperseded ? { cleanSuperseded } : {} }
6083
- };
6085
+ return { ok: true, mode, spec };
6084
6086
  }
6085
6087
  }
6086
6088
  }
6087
- function supersededTokens(items) {
6088
- const r = residentCost(items.map((i) => ({ source: i.source, target: i.target })));
6089
- return r.rules + r.agentDescriptors + r.skillDescriptors;
6090
- }
6091
- function supersededCause(projectDir, spec, log, found) {
6092
- const selected = spec.userOverride?.forceInclude ?? [];
6093
- return selected.filter((id) => {
6094
- const without = {
6095
- ...spec,
6096
- userOverride: {
6097
- forceInclude: selected.filter((x) => x !== id),
6098
- forceExclude: [...spec.userOverride?.forceExclude ?? [], id]
6099
- }
6100
- };
6101
- return findSuperseded(projectDir, buildManifestSpec(without), log).length < found.length;
6102
- });
6103
- }
6104
6089
  function tracksEqual(a, b2) {
6105
6090
  if (a.length !== b2.length) return false;
6106
6091
  const sortedA = [...a].sort();