@uzysjung/agent-harness 26.159.0 → 26.160.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.0",
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,137 @@ 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
+
1214
+ // src/codex/agents-md.ts
1087
1215
  function renameSlashes(text) {
1088
1216
  return text.replaceAll("/uzys:", "/uzys-");
1089
1217
  }
1090
1218
  function renderAgentsMd(params) {
1091
- const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
1219
+ const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
1092
1220
  const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
1093
1221
  return renameSlashes(replaced);
1094
1222
  }
@@ -1312,7 +1440,8 @@ function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills) {
1312
1440
  selectedInternalSkills
1313
1441
  )
1314
1442
  });
1315
- return writer.write(target, rulesOut) ? target : null;
1443
+ const withoutMarkers = rulesOut.split("\n").filter((l) => !/^<!-- uzys-harness:(?:anchor|skills):(?:start|end) -->$/.test(l.trim())).join("\n");
1444
+ return writer.write(target, withoutMarkers) ? target : null;
1316
1445
  }
1317
1446
 
1318
1447
  // src/codex/opt-in.ts
@@ -1457,14 +1586,22 @@ function runCodexTransform(params) {
1457
1586
  claudeMd,
1458
1587
  projectName,
1459
1588
  // ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
1460
- projectContext: withContinuousSkillsNote(
1589
+ // #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다.
1590
+ projectContext: withMarkedContinuousSkillsNote(
1461
1591
  renderFillScaffold("agents-md"),
1462
1592
  selectedInternalSkills
1463
1593
  ),
1464
1594
  // Codex 는 룰 디렉터리가 없다 — 룰이 AGENTS.md 본문에 들어가야 도달한다(§Harness Rules).
1465
1595
  harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
1466
1596
  });
1467
- writer.write(agentsMdPath, agentsMdOut);
1597
+ writer.write(
1598
+ agentsMdPath,
1599
+ mergeAgentsMd({
1600
+ rendered: agentsMdOut,
1601
+ existing: existsSync8(agentsMdPath) ? readFileSync7(agentsMdPath, "utf8") : null,
1602
+ template: agentsTemplate
1603
+ })
1604
+ );
1468
1605
  const configTomlPath = join8(projectDir, ".codex/config.toml");
1469
1606
  writer.write(
1470
1607
  configTomlPath,
@@ -1530,7 +1667,7 @@ function renameSlashes2(text) {
1530
1667
  return text.replaceAll("/uzys:", "/uzys-");
1531
1668
  }
1532
1669
  function renderAgentsMd2(params) {
1533
- const body = params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim();
1670
+ const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
1534
1671
  const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
1535
1672
  return renameSlashes2(replaced);
1536
1673
  }
@@ -1579,7 +1716,8 @@ function runOpencodeTransform(params) {
1579
1716
  claudeMd,
1580
1717
  projectName,
1581
1718
  // ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
1582
- projectContext: withContinuousSkillsNote(
1719
+ // #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다 (codex 와 같은 마커).
1720
+ projectContext: withMarkedContinuousSkillsNote(
1583
1721
  renderFillScaffold("agents-md"),
1584
1722
  selectedInternalSkills
1585
1723
  ),
@@ -1588,7 +1726,14 @@ function runOpencodeTransform(params) {
1588
1726
  // (독립 검증 C-1 실측). 같은 내용을 쓰면 순서가 결과를 바꾸지 않는다.
1589
1727
  harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
1590
1728
  });
1591
- writer.write(agentsMdPath, agentsMdOut);
1729
+ writer.write(
1730
+ agentsMdPath,
1731
+ mergeAgentsMd({
1732
+ rendered: agentsMdOut,
1733
+ existing: existsSync9(agentsMdPath) ? readFileSync8(agentsMdPath, "utf8") : null,
1734
+ template: agentsTemplate
1735
+ })
1736
+ );
1592
1737
  const opencodeJsonPath = join9(projectDir, "opencode.json");
1593
1738
  writer.write(opencodeJsonPath, renderOpencodeJson({ template: opencodeTemplate, mcp }));
1594
1739
  const skillFiles = writeBundledSkillDirs({
@@ -2366,11 +2511,13 @@ function installNewSkillDirs(projectDir, templatesDir, tracks) {
2366
2511
  const foreignOwned = [];
2367
2512
  const log = readInstallLog(projectDir);
2368
2513
  const excluded = new Set(log?.spec.baselineExclude ?? []);
2514
+ const skillExcluded = new Set(log?.spec.skillExclude ?? []);
2369
2515
  if (!(log?.spec.cli ?? ["claude"]).includes("claude")) return { installed, foreignOwned };
2370
2516
  const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
2371
2517
  for (const entry of buildManifest(spec)) {
2372
2518
  if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
2373
2519
  if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
2520
+ if (skillExcluded.has(entry.target.slice(".claude/skills/".length))) continue;
2374
2521
  const target = join12(projectDir, entry.target);
2375
2522
  const foreign = foreignOwnedTarget(projectDir, entry.target);
2376
2523
  if (foreign !== null) {
@@ -2473,6 +2620,11 @@ function recordAnchorBaseline(projectDir, anchor) {
2473
2620
  } catch {
2474
2621
  }
2475
2622
  }
2623
+ function installedBundledSkills(projectDir) {
2624
+ return INTERNAL_BUNDLED_SKILL_IDS.filter(
2625
+ (id) => existsSync13(join12(projectDir, ".claude/skills", id)) || existsSync13(join12(projectDir, ".agents/skills", id))
2626
+ );
2627
+ }
2476
2628
  function refreshExternalCli(projectDir, harnessRoot) {
2477
2629
  const log = readInstallLog(projectDir);
2478
2630
  const baselineExcluded = new Set(log?.spec.baselineExclude ?? []);
@@ -2480,8 +2632,11 @@ function refreshExternalCli(projectDir, harnessRoot) {
2480
2632
  harnessRoot,
2481
2633
  projectDir,
2482
2634
  cli: ALL_CLI_TARGETS,
2483
- selectedInternalSkills: INTERNAL_BUNDLED_SKILL_IDS,
2484
- // 스킬은 **전부** 넘긴다 — 안 깔린 스킬은 대상 파일이 없어 refreshOnly 가 건너뛴다.
2635
+ // 스킬은 **디스크에 있는 것만** 넘긴다. 스킬 *파일*은 전체 목록을 넘겨도 refreshOnly 가
2636
+ // 없는 것을 건너뛰지만, `AGENTS.md` 의 상시 스킬 안내(ADR-085)는 이 목록 그대로 렌더돼
2637
+ // `--without` 으로 뺀 스킬(#505)·opt-in 스킬을 "열어라"고 적었다(실측 2026-09-21). 판정은
2638
+ // `upsertRootImport` 와 같다 — `.claude/skills/<id>` 또는 `.agents/skills/<id>` 가 있으면 깔린 것.
2639
+ selectedInternalSkills: installedBundledSkills(projectDir),
2485
2640
  rules: ALL_RULES.filter((r) => !isBaselineExcluded(`.claude/rules/${r}.md`, baselineExcluded)),
2486
2641
  previousExternal: log?.externalFiles ?? [],
2487
2642
  refreshOnly: true
@@ -4107,10 +4262,10 @@ function formatAssetRows(assets) {
4107
4262
  const idWidth = Math.max(0, ...assets.map((a) => a.id.length));
4108
4263
  const methodWidth = Math.max(0, ...assets.map((a) => a.method.length));
4109
4264
  return assets.map((a) => {
4110
- const marker = a.scope === "global" ? c.yellow("!") : c.green("\u2713");
4265
+ const marker2 = a.scope === "global" ? c.yellow("!") : c.green("\u2713");
4111
4266
  const version = a.version ? c.dim(` v${a.version}`) : "";
4112
4267
  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}`;
4268
+ return ` ${marker2} ${padDisplay(a.id, idWidth)} ${c.dim(padDisplay(a.method, methodWidth))} ${c.dim(a.scope)}${version}${note}`;
4114
4269
  });
4115
4270
  }
4116
4271
  function formatTemplateRows(log, projectDir) {