@uzysjung/agent-harness 26.159.0 → 26.160.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  CATEGORY_TITLES,
6
6
  CLI_BASES,
7
7
  CONTINUOUS_SKILLS,
8
+ CONTINUOUS_SKILLS_HEADING,
8
9
  DEFAULT_OPTIONS,
9
10
  EXTERNAL_ASSETS,
10
11
  HARNESS_ANCHOR_FILE,
@@ -57,6 +58,7 @@ import {
57
58
  listFilesRecursive,
58
59
  mergeExternalFiles,
59
60
  readInstallLog,
61
+ renderContinuousSkillsNote,
60
62
  renderFillScaffold,
61
63
  residentCost,
62
64
  resolveBundleRoot,
@@ -67,7 +69,7 @@ import {
67
69
  upsertHarnessImport,
68
70
  withContinuousSkillsNote,
69
71
  writeInstallLog
70
- } from "./chunk-FTL34NT4.js";
72
+ } from "./chunk-ZXWOY5VO.js";
71
73
 
72
74
  // node_modules/sisteransi/src/index.js
73
75
  var require_src = __commonJS({
@@ -752,7 +754,7 @@ var cac = (name = "") => new CAC(name);
752
754
  // package.json
753
755
  var package_default = {
754
756
  name: "@uzysjung/agent-harness",
755
- version: "26.159.0",
757
+ version: "26.160.1",
756
758
  description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
757
759
  type: "module",
758
760
  publishConfig: {
@@ -1084,11 +1086,170 @@ import { basename, join as join6 } from "path";
1084
1086
 
1085
1087
  // src/codex/agents-md.ts
1086
1088
  init_esm_shims();
1089
+
1090
+ // src/agents-md-merge.ts
1091
+ init_esm_shims();
1092
+ var marker = (name) => ({
1093
+ start: `<!-- uzys-harness:${name}:start -->`,
1094
+ end: `<!-- uzys-harness:${name}:end -->`
1095
+ });
1096
+ var ANCHOR_BLOCK = marker("anchor");
1097
+ var SKILLS_BLOCK = marker("skills");
1098
+ var CONTEXT_SECTION = "Project Context";
1099
+ var RULES_SECTION = "Project Rules";
1100
+ function wrapHarnessBlock(m, body) {
1101
+ return `${m.start}
1102
+ ${body}
1103
+ ${m.end}`;
1104
+ }
1105
+ function withMarkedContinuousSkillsNote(scaffold, selectedInternalSkills) {
1106
+ const note = renderContinuousSkillsNote(selectedInternalSkills);
1107
+ return note ? `${scaffold}
1108
+
1109
+ ${wrapHarnessBlock(SKILLS_BLOCK, note)}` : scaffold;
1110
+ }
1111
+ function sectionNames(template) {
1112
+ return new Set([...template.matchAll(/^## (.+)$/gm)].map((m) => (m[1] ?? "").trim()));
1113
+ }
1114
+ function headingName(line, names) {
1115
+ const name = /^## (.+?)\s*$/.exec(line.replace(/\r$/, ""))?.[1];
1116
+ return name !== void 0 && names.has(name) ? name : null;
1117
+ }
1118
+ function sectionBody(text, names, want) {
1119
+ const lines = text.split("\n");
1120
+ let start = -1;
1121
+ for (const [i, line] of lines.entries()) {
1122
+ const name = headingName(line, names);
1123
+ if (name === null) continue;
1124
+ if (start === -1) {
1125
+ if (name === want) start = i;
1126
+ } else {
1127
+ return lines.slice(start + 1, i);
1128
+ }
1129
+ }
1130
+ return start === -1 ? null : lines.slice(start + 1);
1131
+ }
1132
+ function assemble(rendered, names, bodies) {
1133
+ const out = [];
1134
+ let replacing = false;
1135
+ for (const line of rendered.split("\n")) {
1136
+ const name = headingName(line, names);
1137
+ if (name !== null) {
1138
+ out.push(line);
1139
+ const override = bodies.get(name);
1140
+ replacing = override !== void 0;
1141
+ if (override !== void 0) out.push(...override);
1142
+ continue;
1143
+ }
1144
+ if (!replacing) out.push(line);
1145
+ }
1146
+ return out.join("\n");
1147
+ }
1148
+ function blockRange(lines, m) {
1149
+ const start = lines.findIndex((l) => l.trim() === m.start);
1150
+ if (start === -1) return null;
1151
+ const end = lines.findIndex((l, i) => i > start && l.trim() === m.end);
1152
+ return end === -1 ? null : [start, end];
1153
+ }
1154
+ function trimTrailingBlanks(lines) {
1155
+ const out = [...lines];
1156
+ while (out.length > 0 && (out.at(-1) ?? "").trim() === "") out.pop();
1157
+ return out;
1158
+ }
1159
+ function refreshBlock(existing, rendered, m, legacyHeading) {
1160
+ const at2 = blockRange(rendered, m);
1161
+ const fresh = at2 === null ? null : rendered.slice(at2[0], at2[1] + 1);
1162
+ const here = blockRange(existing, m);
1163
+ if (here !== null) {
1164
+ const head2 = existing.slice(0, here[0]);
1165
+ const tail2 = existing.slice(here[1] + 1);
1166
+ if (fresh === null) {
1167
+ const kept = trimTrailingBlanks(head2);
1168
+ return tail2.length > 0 ? [...kept, ...tail2] : [...kept, ""];
1169
+ }
1170
+ return [...head2, ...fresh, ...tail2];
1171
+ }
1172
+ const legacyAt = legacyHeading === void 0 ? -1 : existing.findIndex((l) => l.replace(/\r$/, "").trim() === legacyHeading);
1173
+ if (legacyAt === -1) {
1174
+ const kept = trimTrailingBlanks(existing);
1175
+ return fresh === null ? [...kept, ""] : [...kept, "", ...fresh, ""];
1176
+ }
1177
+ const head = trimTrailingBlanks(existing.slice(0, legacyAt));
1178
+ const tail = existing.slice(legacyNoteEnd(existing, legacyAt));
1179
+ while (tail.length > 0 && (tail[0] ?? "").trim() === "") tail.shift();
1180
+ const body = fresh === null ? [...head] : [...head, "", ...fresh];
1181
+ return tail.length > 0 ? [...body, "", ...tail] : [...body, ""];
1182
+ }
1183
+ function legacyNoteEnd(lines, headingAt) {
1184
+ let i = headingAt + 1;
1185
+ while (i < lines.length) {
1186
+ const l = (lines[i] ?? "").replace(/\r$/, "").trim();
1187
+ const isNote = l === "" || l.startsWith("- `") || l.startsWith("These installed skills apply");
1188
+ if (!isNote) break;
1189
+ i++;
1190
+ }
1191
+ return i;
1192
+ }
1193
+ function mergeAgentsMd(params) {
1194
+ const { rendered, existing, template } = params;
1195
+ if (existing === null) return rendered;
1196
+ const names = sectionNames(template);
1197
+ const bodies = /* @__PURE__ */ new Map();
1198
+ const contextNow = sectionBody(existing, names, CONTEXT_SECTION);
1199
+ const contextNew = sectionBody(rendered, names, CONTEXT_SECTION);
1200
+ if (contextNow !== null && contextNew !== null) {
1201
+ bodies.set(
1202
+ CONTEXT_SECTION,
1203
+ refreshBlock(contextNow, contextNew, SKILLS_BLOCK, CONTINUOUS_SKILLS_HEADING)
1204
+ );
1205
+ }
1206
+ const rulesNow = sectionBody(existing, names, RULES_SECTION);
1207
+ const rulesNew = sectionBody(rendered, names, RULES_SECTION);
1208
+ if (rulesNow !== null && rulesNew !== null && blockRange(rulesNow, ANCHOR_BLOCK) !== null) {
1209
+ bodies.set(RULES_SECTION, refreshBlock(rulesNow, rulesNew, ANCHOR_BLOCK));
1210
+ }
1211
+ return bodies.size === 0 ? rendered : assemble(rendered, names, bodies);
1212
+ }
1213
+ function installerLines(body, m) {
1214
+ if (body === null) return [];
1215
+ const at2 = blockRange(body, m);
1216
+ return at2 === null ? [...body] : [...body.slice(0, at2[0]), ...body.slice(at2[1] + 1)];
1217
+ }
1218
+ function compact(lines) {
1219
+ return lines.map((l) => l.replace(/\r$/, "").trimEnd()).filter((l) => l.trim() !== "");
1220
+ }
1221
+ function trimBlankEdges(lines) {
1222
+ const out = trimTrailingBlanks(lines);
1223
+ while (out.length > 0 && (out[0] ?? "").trim() === "") out.shift();
1224
+ return out;
1225
+ }
1226
+ function stripHarnessFromAgentsMd(params) {
1227
+ const { existing, template } = params;
1228
+ const lines = existing.split("\n");
1229
+ if (blockRange(lines, ANCHOR_BLOCK) === null && blockRange(lines, SKILLS_BLOCK) === null) {
1230
+ return null;
1231
+ }
1232
+ const names = sectionNames(template);
1233
+ const context = installerLines(sectionBody(existing, names, CONTEXT_SECTION), SKILLS_BLOCK);
1234
+ const rules = installerLines(sectionBody(existing, names, RULES_SECTION), ANCHOR_BLOCK);
1235
+ const scaffold = compact(renderFillScaffold("agents-md").split("\n"));
1236
+ const contextFilled = compact(context).join("\n") !== scaffold.join("\n");
1237
+ const rulesFilled = compact(rules).length > 0;
1238
+ if (!contextFilled && !rulesFilled) return null;
1239
+ const out = [];
1240
+ const title = (lines[0] ?? "").replace(/\r$/, "");
1241
+ if (/^# /.test(title)) out.push(title, "");
1242
+ out.push(`## ${CONTEXT_SECTION}`, "", ...trimBlankEdges(context), "");
1243
+ if (rulesFilled) out.push(`## ${RULES_SECTION}`, "", ...trimBlankEdges(rules), "");
1244
+ return out.join("\n");
1245
+ }
1246
+
1247
+ // src/codex/agents-md.ts
1087
1248
  function renameSlashes(text) {
1088
1249
  return text.replaceAll("/uzys:", "/uzys-");
1089
1250
  }
1090
1251
  function renderAgentsMd(params) {
1091
- const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
1252
+ const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
1092
1253
  const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
1093
1254
  return renameSlashes(replaced);
1094
1255
  }
@@ -1312,7 +1473,8 @@ function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills) {
1312
1473
  selectedInternalSkills
1313
1474
  )
1314
1475
  });
1315
- return writer.write(target, rulesOut) ? target : null;
1476
+ const withoutMarkers = rulesOut.split("\n").filter((l) => !/^<!-- uzys-harness:(?:anchor|skills):(?:start|end) -->$/.test(l.trim())).join("\n");
1477
+ return writer.write(target, withoutMarkers) ? target : null;
1316
1478
  }
1317
1479
 
1318
1480
  // src/codex/opt-in.ts
@@ -1457,14 +1619,22 @@ function runCodexTransform(params) {
1457
1619
  claudeMd,
1458
1620
  projectName,
1459
1621
  // ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
1460
- projectContext: withContinuousSkillsNote(
1622
+ // #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다.
1623
+ projectContext: withMarkedContinuousSkillsNote(
1461
1624
  renderFillScaffold("agents-md"),
1462
1625
  selectedInternalSkills
1463
1626
  ),
1464
1627
  // Codex 는 룰 디렉터리가 없다 — 룰이 AGENTS.md 본문에 들어가야 도달한다(§Harness Rules).
1465
1628
  harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
1466
1629
  });
1467
- writer.write(agentsMdPath, agentsMdOut);
1630
+ writer.write(
1631
+ agentsMdPath,
1632
+ mergeAgentsMd({
1633
+ rendered: agentsMdOut,
1634
+ existing: existsSync8(agentsMdPath) ? readFileSync7(agentsMdPath, "utf8") : null,
1635
+ template: agentsTemplate
1636
+ })
1637
+ );
1468
1638
  const configTomlPath = join8(projectDir, ".codex/config.toml");
1469
1639
  writer.write(
1470
1640
  configTomlPath,
@@ -1530,7 +1700,7 @@ function renameSlashes2(text) {
1530
1700
  return text.replaceAll("/uzys:", "/uzys-");
1531
1701
  }
1532
1702
  function renderAgentsMd2(params) {
1533
- const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
1703
+ const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
1534
1704
  const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
1535
1705
  return renameSlashes2(replaced);
1536
1706
  }
@@ -1579,7 +1749,8 @@ function runOpencodeTransform(params) {
1579
1749
  claudeMd,
1580
1750
  projectName,
1581
1751
  // ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
1582
- projectContext: withContinuousSkillsNote(
1752
+ // #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다 (codex 와 같은 마커).
1753
+ projectContext: withMarkedContinuousSkillsNote(
1583
1754
  renderFillScaffold("agents-md"),
1584
1755
  selectedInternalSkills
1585
1756
  ),
@@ -1588,7 +1759,14 @@ function runOpencodeTransform(params) {
1588
1759
  // (독립 검증 C-1 실측). 같은 내용을 쓰면 순서가 결과를 바꾸지 않는다.
1589
1760
  harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
1590
1761
  });
1591
- writer.write(agentsMdPath, agentsMdOut);
1762
+ writer.write(
1763
+ agentsMdPath,
1764
+ mergeAgentsMd({
1765
+ rendered: agentsMdOut,
1766
+ existing: existsSync9(agentsMdPath) ? readFileSync8(agentsMdPath, "utf8") : null,
1767
+ template: agentsTemplate
1768
+ })
1769
+ );
1592
1770
  const opencodeJsonPath = join9(projectDir, "opencode.json");
1593
1771
  writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
1594
1772
  const skillFiles = writeBundledSkillDirs({
@@ -2366,11 +2544,13 @@ function installNewSkillDirs(projectDir, templatesDir, tracks) {
2366
2544
  const foreignOwned = [];
2367
2545
  const log = readInstallLog(projectDir);
2368
2546
  const excluded = new Set(log?.spec.baselineExclude ?? []);
2547
+ const skillExcluded = new Set(log?.spec.skillExclude ?? []);
2369
2548
  if (!(log?.spec.cli ?? ["claude"]).includes("claude")) return { installed, foreignOwned };
2370
2549
  const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
2371
2550
  for (const entry of buildManifest(spec)) {
2372
2551
  if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
2373
2552
  if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
2553
+ if (skillExcluded.has(entry.target.slice(".claude/skills/".length))) continue;
2374
2554
  const target = join12(projectDir, entry.target);
2375
2555
  const foreign = foreignOwnedTarget(projectDir, entry.target);
2376
2556
  if (foreign !== null) {
@@ -2473,15 +2653,31 @@ function recordAnchorBaseline(projectDir, anchor) {
2473
2653
  } catch {
2474
2654
  }
2475
2655
  }
2656
+ function installedBundledSkills(projectDir) {
2657
+ return INTERNAL_BUNDLED_SKILL_IDS.filter(
2658
+ (id) => existsSync13(join12(projectDir, ".claude/skills", id)) || existsSync13(join12(projectDir, ".agents/skills", id))
2659
+ );
2660
+ }
2661
+ function installedCliTargets(log) {
2662
+ if (log === null) return ALL_CLI_TARGETS;
2663
+ return ALL_CLI_TARGETS.filter((cli2) => {
2664
+ if (cli2 === "codex") return log.templates.codexDir !== void 0;
2665
+ if (cli2 === "opencode") return log.templates.opencodeDir !== void 0;
2666
+ return true;
2667
+ });
2668
+ }
2476
2669
  function refreshExternalCli(projectDir, harnessRoot) {
2477
2670
  const log = readInstallLog(projectDir);
2478
2671
  const baselineExcluded = new Set(log?.spec.baselineExclude ?? []);
2479
2672
  const result = runCliTransforms({
2480
2673
  harnessRoot,
2481
2674
  projectDir,
2482
- cli: ALL_CLI_TARGETS,
2483
- selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS,
2484
- // 스킬은 **전부** 넘긴다 — 안 깔린 스킬은 대상 파일이 없어 refreshOnly 가 건너뛴다.
2675
+ cli: installedCliTargets(log),
2676
+ // 스킬은 **디스크에 있는 것만** 넘긴다. 스킬 *파일*은 전체 목록을 넘겨도 refreshOnly 가
2677
+ // 없는 것을 건너뛰지만, `AGENTS.md` 의 상시 스킬 안내(ADR-085)는 이 목록 그대로 렌더돼
2678
+ // `--without` 으로 뺀 스킬(#505)·opt-in 스킬을 "열어라"고 적었다(실측 2026-09-21). 판정은
2679
+ // `upsertRootImport` 와 같다 — `.claude/skills/<id>` 또는 `.agents/skills/<id>` 가 있으면 깔린 것.
2680
+ selectedInternalSkills: installedBundledSkills(projectDir),
2485
2681
  rules: ALL_RULES.filter((r) => !isBaselineExcluded(`.claude/rules/${r}.md`, baselineExcluded)),
2486
2682
  previousExternal: log?.externalFiles ?? [],
2487
2683
  refreshOnly: true
@@ -4107,10 +4303,10 @@ function formatAssetRows(assets) {
4107
4303
  const idWidth = Math.max(0, ...assets.map((a) => a.id.length));
4108
4304
  const methodWidth = Math.max(0, ...assets.map((a) => a.method.length));
4109
4305
  return assets.map((a) => {
4110
- const marker = a.scope === "global" ? c.yellow("!") : c.green("\u2713");
4306
+ const marker2 = a.scope === "global" ? c.yellow("!") : c.green("\u2713");
4111
4307
  const version = a.version ? c.dim(` v${a.version}`) : "";
4112
4308
  const note = a.scope === "global" ? c.yellow(" (manual removal \u2014 D16)") : "";
4113
- return ` ${marker} ${padDisplay(a.id, idWidth)} ${c.dim(padDisplay(a.method, methodWidth))} ${c.dim(a.scope)}${version}${note}`;
4309
+ return ` ${marker2} ${padDisplay(a.id, idWidth)} ${c.dim(padDisplay(a.method, methodWidth))} ${c.dim(a.scope)}${version}${note}`;
4114
4310
  });
4115
4311
  }
4116
4312
  function formatTemplateRows(log, projectDir) {
@@ -5255,6 +5451,7 @@ function uninstallAction(options, deps = {}) {
5255
5451
  const spawn = deps.spawn ?? defaultSpawn2;
5256
5452
  const rm = deps.rm ?? defaultRm;
5257
5453
  const writeLog = deps.writeLog ?? writeInstallLog;
5454
+ const harnessRoot = (deps.resolveHarnessRoot ?? defaultHarnessRoot)();
5258
5455
  const projectDir = resolve4(options.projectDir ?? process.cwd());
5259
5456
  const installLog = readInstallLog(projectDir);
5260
5457
  if (!installLog) {
@@ -5283,7 +5480,14 @@ function uninstallAction(options, deps = {}) {
5283
5480
  const plan = planReverse(targetAssets, spawn);
5284
5481
  for (const line of headerLines(installLog, selectedIds, targetAssets.length)) log(line);
5285
5482
  if (options.dryRun) {
5286
- for (const line of dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles)) {
5483
+ for (const line of dryRunLines(
5484
+ plan,
5485
+ installLog,
5486
+ projectDir,
5487
+ keepTemplates,
5488
+ rootFiles,
5489
+ harnessRoot
5490
+ )) {
5287
5491
  log(line);
5288
5492
  }
5289
5493
  exit(0);
@@ -5295,7 +5499,8 @@ function uninstallAction(options, deps = {}) {
5295
5499
  const { rootClaudeMdKept, importStripped, external } = removeTemplates(
5296
5500
  installLog,
5297
5501
  projectDir,
5298
- rm
5502
+ rm,
5503
+ harnessRoot
5299
5504
  );
5300
5505
  log(` ${status.success(`templates removed: ${formatTemplateList(installLog)}`)}`);
5301
5506
  for (const line of externalRemovalLines(external)) log(line);
@@ -5402,7 +5607,7 @@ function settleLog(ctx, io) {
5402
5607
  }
5403
5608
  return false;
5404
5609
  }
5405
- function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles) {
5610
+ function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles, harnessRoot) {
5406
5611
  const lines = [c.yellow("[DRY RUN] reverse list (\uC2E4\uC81C \uBCC0\uACBD \uC5C6\uC74C):"), ""];
5407
5612
  if (plan.reverseSteps.length === 0) {
5408
5613
  lines.push(c.dim(" (no project-scope assets to reverse)"));
@@ -5424,7 +5629,7 @@ function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles) {
5424
5629
  if (hasRootImport(projectDir)) {
5425
5630
  lines.push(" \u25CB strip harness @import from CLAUDE.md (\uBCF8\uBB38 \uBCF4\uC874)");
5426
5631
  }
5427
- lines.push(...previewExternalLines(installLog, projectDir));
5632
+ lines.push(...previewExternalLines(installLog, projectDir, harnessRoot));
5428
5633
  }
5429
5634
  lines.push(...advisoryLines(plan, projectDir, rootFiles), "");
5430
5635
  return lines;
@@ -5546,11 +5751,11 @@ function buildGlobalAdvisoryCmd(asset) {
5546
5751
  return "(no standard reverse \u2014 manual)";
5547
5752
  }
5548
5753
  }
5549
- function removeTemplates(log, projectDir, rm) {
5754
+ function removeTemplates(log, projectDir, rm, harnessRoot) {
5550
5755
  rm(join16(projectDir, log.templates.claudeDir));
5551
5756
  if (log.templates.codexDir) rm(join16(projectDir, log.templates.codexDir));
5552
5757
  if (log.templates.opencodeDir) rm(join16(projectDir, log.templates.opencodeDir));
5553
- const external = removeExternalFiles(log, projectDir, rm);
5758
+ const external = removeExternalFiles(log, projectDir, rm, harnessRoot);
5554
5759
  const importStripped = stripRootImport(projectDir);
5555
5760
  const rootMd = log.templates.rootClaudeMd;
5556
5761
  if (rootMd) {
@@ -5560,22 +5765,55 @@ function removeTemplates(log, projectDir, rm) {
5560
5765
  }
5561
5766
  return { rootClaudeMdKept: false, importStripped, external };
5562
5767
  }
5563
- function removeExternalFiles(log, projectDir, rm) {
5768
+ var AGENTS_MD = "AGENTS.md";
5769
+ function readAgentsMdTemplate(harnessRoot, log) {
5770
+ const flavor = log.templates.opencodeDir ? "opencode" : "codex";
5771
+ try {
5772
+ return readFileSync15(join16(harnessRoot, "templates", flavor, "AGENTS.md.template"), "utf8");
5773
+ } catch {
5774
+ return null;
5775
+ }
5776
+ }
5777
+ function agentsMdRemainder(current, harnessRoot, log) {
5778
+ const template = readAgentsMdTemplate(harnessRoot, log);
5779
+ if (template === null) return { ok: false };
5780
+ return { ok: true, remainder: stripHarnessFromAgentsMd({ existing: current, template }) };
5781
+ }
5782
+ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
5564
5783
  const removed = [];
5565
5784
  const kept = [];
5785
+ const stripped = [];
5786
+ const unjudged = [];
5566
5787
  for (const { path, sha256 } of log.externalFiles ?? []) {
5567
5788
  const abs = join16(projectDir, path);
5568
5789
  if (!existsSync17(abs)) continue;
5569
5790
  if (!lstatSync2(abs).isFile()) continue;
5570
- if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
5791
+ const current = readFileSync15(abs, "utf8");
5792
+ if (hashContent(current) !== sha256) {
5571
5793
  kept.push(path);
5572
5794
  continue;
5573
5795
  }
5796
+ if (path === AGENTS_MD) {
5797
+ const verdict = agentsMdRemainder(current, harnessRoot, log);
5798
+ if (!verdict.ok) {
5799
+ unjudged.push(path);
5800
+ continue;
5801
+ }
5802
+ if (verdict.remainder !== null) {
5803
+ try {
5804
+ writeFileSync7(abs, verdict.remainder, "utf8");
5805
+ stripped.push(path);
5806
+ } catch {
5807
+ unjudged.push(path);
5808
+ }
5809
+ continue;
5810
+ }
5811
+ }
5574
5812
  rm(abs);
5575
5813
  removed.push(path);
5576
5814
  }
5577
5815
  for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname5(join16(projectDir, path)));
5578
- return { removed, kept };
5816
+ return { removed, kept, stripped, unjudged };
5579
5817
  }
5580
5818
  function pruneEmptyDirsUpward(projectDir, startDir) {
5581
5819
  let cursor = startDir;
@@ -5602,16 +5840,28 @@ function stripRootImport(projectDir) {
5602
5840
  return false;
5603
5841
  }
5604
5842
  }
5605
- function previewExternalLines(installLog, projectDir) {
5843
+ function previewExternalLines(installLog, projectDir, harnessRoot) {
5606
5844
  const lines = [];
5607
5845
  let removable = 0;
5608
5846
  for (const { path, sha256 } of installLog.externalFiles ?? []) {
5609
5847
  const abs = join16(projectDir, path);
5610
5848
  if (!existsSync17(abs)) continue;
5611
- if (hashContent(readFileSync15(abs, "utf8")) !== sha256) {
5849
+ const current = readFileSync15(abs, "utf8");
5850
+ if (hashContent(current) !== sha256) {
5612
5851
  lines.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
5613
5852
  continue;
5614
5853
  }
5854
+ if (path === AGENTS_MD) {
5855
+ const verdict = agentsMdRemainder(current, harnessRoot, installLog);
5856
+ if (!verdict.ok) {
5857
+ lines.push(` \u25CB keep ${path} (harness sections could not be separated \u2014 preserved)`);
5858
+ continue;
5859
+ }
5860
+ if (verdict.remainder !== null) {
5861
+ lines.push(` \u25CB strip harness sections from ${path} (\uBCF8\uBB38 \uBCF4\uC874)`);
5862
+ continue;
5863
+ }
5864
+ }
5615
5865
  removable += 1;
5616
5866
  }
5617
5867
  if (removable > 0) lines.unshift(` \u25CB remove ${removable} CLI output file(s)`);
@@ -5622,6 +5872,14 @@ function externalRemovalLines(external) {
5622
5872
  if (external.removed.length > 0) {
5623
5873
  lines.push(` ${status.success(`CLI outputs removed: ${external.removed.length} file(s)`)}`);
5624
5874
  }
5875
+ for (const path of external.stripped) {
5876
+ lines.push(` ${status.success(`${path} \u2014 harness sections removed (\uBCF8\uBB38 \uBCF4\uC874)`)}`);
5877
+ }
5878
+ for (const path of external.unjudged) {
5879
+ lines.push(
5880
+ ` ${c.yellow("\u2298")} ${path} kept \u2014 harness sections could not be separated (template unreadable or write failed). Remove manually if intended.`
5881
+ );
5882
+ }
5625
5883
  for (const path of external.kept) {
5626
5884
  lines.push(
5627
5885
  ` ${c.yellow("\u2298")} ${path} kept \u2014 modified since install. Remove manually if intended.`