@uzysjung/agent-harness 26.164.0 → 26.165.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
@@ -4,6 +4,7 @@ import {
4
4
  CATEGORY_TITLES,
5
5
  CLI_BASES,
6
6
  CLI_BASE_SORT_ORDER,
7
+ CLI_DISPLAY_NAMES,
7
8
  CONTINUOUS_SKILLS,
8
9
  CONTINUOUS_SKILLS_HEADING,
9
10
  DEFAULT_OPTIONS,
@@ -27,6 +28,7 @@ import {
27
28
  UPDATE_GROUPS,
28
29
  __commonJS,
29
30
  __toESM,
31
+ anchorSurface,
30
32
  anyTrack,
31
33
  appendSelection,
32
34
  assetCliSupport,
@@ -43,12 +45,15 @@ import {
43
45
  claudeTargets,
44
46
  collectPolicyHashes,
45
47
  collectSkillHashes,
48
+ continuousNoteEnd,
46
49
  copyBackupDir,
47
50
  copyDir,
48
51
  copyFile,
49
52
  corruptedInstallLogMessage,
50
53
  createOutsideGuard,
51
54
  dirTreesIdentical,
55
+ dropContinuousSkillsNote,
56
+ dropHarnessQuoteBlocks,
52
57
  ensureDir,
53
58
  ensureProjectSkeleton,
54
59
  experimentalOptInCandidates,
@@ -68,10 +73,12 @@ import {
68
73
  isKeyId,
69
74
  isOutsideProject,
70
75
  isTrack,
76
+ isUnfilledScaffold,
71
77
  legacyDroppedKeys,
72
78
  legacyInstallLogPath,
73
79
  legacyReleasedCatalog,
74
80
  listFilesRecursive,
81
+ makeResidentCost,
75
82
  memoTargets,
76
83
  mergeExternalFiles,
77
84
  mergeOutside,
@@ -94,9 +101,8 @@ import {
94
101
  summarizeContextCost,
95
102
  targetsInclude,
96
103
  upsertHarnessImport,
97
- withContinuousSkillsNote,
98
104
  writeInstallLog
99
- } from "./chunk-X7NSQHXL.js";
105
+ } from "./chunk-QHBJ7PMU.js";
100
106
 
101
107
  // node_modules/sisteransi/src/index.js
102
108
  var require_src = __commonJS({
@@ -780,8 +786,8 @@ var cac = (name = "") => new CAC(name);
780
786
 
781
787
  // src/commands/install.ts
782
788
  init_esm_shims();
783
- import { rmSync as rmSync3 } from "fs";
784
- import { join as join28, resolve as resolve2 } from "path";
789
+ import { rmSync as rmSync4 } from "fs";
790
+ import { join as join31, resolve as resolve2 } from "path";
785
791
  import { fileURLToPath } from "url";
786
792
 
787
793
  // src/adapters/index.ts
@@ -1427,6 +1433,7 @@ var AGENTS_BLOCK_NAME = "agents";
1427
1433
  var AGENTS_BLOCK = marker(AGENTS_BLOCK_NAME);
1428
1434
  var CONTEXT_SECTION = "Project Context";
1429
1435
  var RULES_SECTION = "Project Rules";
1436
+ var HARNESS_RULES_SECTION = "Harness Rules";
1430
1437
  function wrapHarnessBlock(m, body) {
1431
1438
  return `${m.start}
1432
1439
  ${body}
@@ -1505,21 +1512,11 @@ function refreshBlock(existing, rendered, m, legacyHeading) {
1505
1512
  return fresh === null ? [...kept, ""] : [...kept, "", ...fresh, ""];
1506
1513
  }
1507
1514
  const head = trimTrailingBlanks(existing.slice(0, legacyAt));
1508
- const tail = existing.slice(legacyNoteEnd(existing, legacyAt));
1515
+ const tail = existing.slice(continuousNoteEnd(existing, legacyAt));
1509
1516
  while (tail.length > 0 && (tail[0] ?? "").trim() === "") tail.shift();
1510
1517
  const body = fresh === null ? [...head] : [...head, "", ...fresh];
1511
1518
  return tail.length > 0 ? [...body, "", ...tail] : [...body, ""];
1512
1519
  }
1513
- function legacyNoteEnd(lines2, headingAt) {
1514
- let i = headingAt + 1;
1515
- while (i < lines2.length) {
1516
- const l = (lines2[i] ?? "").replace(/\r$/, "").trim();
1517
- const isNote = l === "" || l.startsWith("- `") || l.startsWith("These installed skills apply");
1518
- if (!isNote) break;
1519
- i++;
1520
- }
1521
- return i;
1522
- }
1523
1520
  var ANCHOR_TITLE_FALLBACK = "Working Principles";
1524
1521
  function anchorTitle(claudeMd) {
1525
1522
  const first = claudeMd.split("\n")[0] ?? "";
@@ -1555,7 +1552,8 @@ function mergeAgentsMd(params) {
1555
1552
  const bodies = /* @__PURE__ */ new Map();
1556
1553
  const contextNow = sectionBody(existing, names, CONTEXT_SECTION);
1557
1554
  const contextNew = sectionBody(rendered, names, CONTEXT_SECTION);
1558
- if (contextNow !== null && contextNew !== null) {
1555
+ const seedOverUnfilled = params.replaceUnfilledContext === true && contextNow !== null && isUnfilledScaffold(installerLines(contextNow, SKILLS_BLOCK));
1556
+ if (contextNow !== null && contextNew !== null && !seedOverUnfilled) {
1559
1557
  bodies.set(
1560
1558
  CONTEXT_SECTION,
1561
1559
  refreshBlock(contextNow, contextNew, SKILLS_BLOCK, CONTINUOUS_SKILLS_HEADING)
@@ -1568,24 +1566,30 @@ function mergeAgentsMd(params) {
1568
1566
  }
1569
1567
  const merged = bodies.size === 0 ? rendered : assemble(rendered, names, bodies);
1570
1568
  const mergedText = merged.replace(/\r/g, "");
1571
- const extras = extraTopLevelSections(existing, names, rendered, template).filter(
1572
- (e2) => !mergedText.includes(e2.join("\n").replace(/\r/g, ""))
1573
- );
1569
+ const extras = extraTopLevelSections(
1570
+ existing,
1571
+ names,
1572
+ rendered,
1573
+ template,
1574
+ params.harnessRuleHeadings ?? /* @__PURE__ */ new Set()
1575
+ ).filter((e2) => !mergedText.includes(e2.join("\n").replace(/\r/g, "")));
1574
1576
  if (extras.length === 0) return merged;
1575
1577
  return `${merged.replace(/\n+$/, "\n")}${extras.map((e2) => e2.join("\n")).join("\n")}`.replace(
1576
1578
  /\n+$/,
1577
1579
  "\n"
1578
1580
  );
1579
1581
  }
1580
- function extraTopLevelSections(existing, names, rendered, template) {
1582
+ function extraTopLevelSections(existing, names, rendered, template, harnessRuleHeadings) {
1581
1583
  const out = [];
1582
1584
  let current = null;
1585
+ let inHarnessRules = false;
1583
1586
  for (const line of existing.split("\n")) {
1584
1587
  const bare = line.replace(/\r$/, "");
1585
1588
  const name = /^## (.+?)\s*$/.exec(bare)?.[1];
1586
1589
  if (name !== void 0) {
1587
1590
  if (current !== null) out.push(current);
1588
- const harnessAuthored = names.has(name) || rendered.includes(bare) || template.includes(bare) || line.trim() === CONTINUOUS_SKILLS_HEADING;
1591
+ if (names.has(name)) inHarnessRules = name === HARNESS_RULES_SECTION;
1592
+ const harnessAuthored = names.has(name) || inHarnessRules && harnessRuleHeadings.has(bare.trim()) || rendered.includes(bare) || template.includes(bare) || line.trim() === CONTINUOUS_SKILLS_HEADING;
1589
1593
  current = harnessAuthored ? null : [line];
1590
1594
  continue;
1591
1595
  }
@@ -1616,6 +1620,30 @@ function projectContextInstallerLines(params) {
1616
1620
  const body = sectionBody(existing, sectionNames(template), CONTEXT_SECTION);
1617
1621
  return body === null ? null : installerLines(body, SKILLS_BLOCK);
1618
1622
  }
1623
+ function installerSectionsInHarnessArea(existing, names, harnessRuleHeadings) {
1624
+ const out = [];
1625
+ let current = null;
1626
+ let region = null;
1627
+ for (const line of existing.split("\n")) {
1628
+ const bare = line.replace(/\r$/, "");
1629
+ const name = /^## (.+?)\s*$/.exec(bare)?.[1];
1630
+ if (name !== void 0) {
1631
+ if (current !== null) out.push(current);
1632
+ current = null;
1633
+ if (names.has(name)) {
1634
+ region = name;
1635
+ continue;
1636
+ }
1637
+ if (region === CONTEXT_SECTION || region === RULES_SECTION) continue;
1638
+ const harnessRule = region === HARNESS_RULES_SECTION && harnessRuleHeadings.has(bare.trim());
1639
+ if (!harnessRule) current = [bare];
1640
+ continue;
1641
+ }
1642
+ if (current !== null) current.push(bare);
1643
+ }
1644
+ if (current !== null) out.push(current);
1645
+ return out.map(trimTrailingBlanks);
1646
+ }
1619
1647
  function stripHarnessFromAgentsMd(params) {
1620
1648
  const { existing, template } = params;
1621
1649
  const lines2 = existing.split("\n");
@@ -1625,15 +1653,22 @@ function stripHarnessFromAgentsMd(params) {
1625
1653
  const names = sectionNames(template);
1626
1654
  const context = installerLines(sectionBody(existing, names, CONTEXT_SECTION), SKILLS_BLOCK);
1627
1655
  const rules = installerLines(sectionBody(existing, names, RULES_SECTION), ANCHOR_BLOCK);
1628
- const scaffold = compact(renderFillScaffold("agents-md").split("\n"));
1629
- const contextFilled = compact(context).join("\n") !== scaffold.join("\n");
1656
+ const contextFilled = !isUnfilledScaffold(context);
1630
1657
  const rulesFilled = compact(rules).length > 0;
1631
- if (!contextFilled && !rulesFilled) return null;
1658
+ const extras = installerSectionsInHarnessArea(
1659
+ existing,
1660
+ names,
1661
+ params.harnessRuleHeadings ?? /* @__PURE__ */ new Set()
1662
+ );
1663
+ if (!contextFilled && !rulesFilled && extras.length === 0) return null;
1632
1664
  const out = [];
1633
1665
  const title = (lines2[0] ?? "").replace(/\r$/, "");
1634
1666
  if (/^# /.test(title)) out.push(title, "");
1635
- out.push(`## ${CONTEXT_SECTION}`, "", ...trimBlankEdges(context), "");
1667
+ if (contextFilled || rulesFilled) {
1668
+ out.push(`## ${CONTEXT_SECTION}`, "", ...trimBlankEdges(context), "");
1669
+ }
1636
1670
  if (rulesFilled) out.push(`## ${RULES_SECTION}`, "", ...trimBlankEdges(rules), "");
1671
+ for (const section of extras) out.push(...section, "");
1637
1672
  return out.join("\n");
1638
1673
  }
1639
1674
 
@@ -2592,6 +2627,33 @@ function segment(body) {
2592
2627
  }
2593
2628
  return items;
2594
2629
  }
2630
+ function same(a, b2) {
2631
+ if (a instanceof Date || b2 instanceof Date) {
2632
+ return a instanceof Date && b2 instanceof Date && a.toISOString() === b2.toISOString();
2633
+ }
2634
+ if (Array.isArray(a)) {
2635
+ return Array.isArray(b2) && a.length === b2.length && a.every((v2, i) => same(v2, b2[i]));
2636
+ }
2637
+ if (isTable(a)) {
2638
+ const keys = Object.keys(a);
2639
+ return isTable(b2) && keys.length === Object.keys(b2).length && keys.every((k2) => Object.hasOwn(b2, k2) && same(a[k2], b2[k2]));
2640
+ }
2641
+ return Object.is(a, b2);
2642
+ }
2643
+ function unchangedUnder(before, after) {
2644
+ if (isTable(before)) {
2645
+ return isTable(after) && Object.keys(before).every(
2646
+ (k2) => Object.hasOwn(after, k2) && unchangedUnder(before[k2], after[k2])
2647
+ );
2648
+ }
2649
+ if (Array.isArray(before)) {
2650
+ return Array.isArray(after) && after.length >= before.length && before.every((v2, i) => same(v2, after[i]));
2651
+ }
2652
+ return same(before, after);
2653
+ }
2654
+ function startsWith(path, prefix) {
2655
+ return prefix.length > 0 && prefix.length <= path.length && prefix.every((s, i) => path[i] === s);
2656
+ }
2595
2657
  function hasPath(obj, path) {
2596
2658
  let cur = obj;
2597
2659
  for (const seg of path) {
@@ -2617,19 +2679,26 @@ function filterRender(render, outside) {
2617
2679
  throw new Error(`toml-region: region ${name} must start with a [table] header`);
2618
2680
  }
2619
2681
  const mine = [];
2682
+ const dropped = [];
2620
2683
  for (const item of items) {
2684
+ if (dropped.some((p2) => startsWith(item.path, p2))) {
2685
+ kept.push(item.name);
2686
+ continue;
2687
+ }
2621
2688
  const taken = item.header !== "array" && hasPath(installer, item.path);
2622
2689
  side.push(item.text);
2623
- if (taken || readToml(doc()) === null) {
2690
+ const merged = taken ? null : readToml(doc());
2691
+ if (merged === null || !unchangedUnder(installer, merged)) {
2624
2692
  side.pop();
2625
2693
  kept.push(item.name);
2694
+ dropped.push(item.path);
2626
2695
  continue;
2627
2696
  }
2628
2697
  mine.push(item.text);
2629
2698
  }
2630
2699
  if (mine.length > 0) out.set(name, mine.join("\n"));
2631
2700
  }
2632
- return { render: out, kept };
2701
+ return { render: out, kept: [...new Set(kept)] };
2633
2702
  }
2634
2703
  function regionText(name, body) {
2635
2704
  return `# uzys-harness:${name}:start
@@ -3122,22 +3191,22 @@ var CLI_OWNERSHIP = {
3122
3191
  ],
3123
3192
  codex: [
3124
3193
  { path: ".codex/", kind: "dir", sharedWith: [] },
3125
- { path: "AGENTS.md", kind: "recorded", sharedWith: ["opencode"] },
3194
+ { path: "AGENTS.md", kind: "recorded", sharedWith: ["opencode", "antigravity"] },
3126
3195
  { path: ".agents/skills/", kind: "recorded", sharedWith: ["opencode", "antigravity"] },
3127
3196
  ...SHARED_BY_ALL("codex")
3128
3197
  ],
3129
3198
  opencode: [
3130
3199
  { path: ".opencode/", kind: "dir", sharedWith: [] },
3131
3200
  { path: "opencode.json", kind: "recorded", sharedWith: [] },
3132
- { path: "AGENTS.md", kind: "recorded", sharedWith: ["codex"] },
3201
+ { path: "AGENTS.md", kind: "recorded", sharedWith: ["codex", "antigravity"] },
3133
3202
  { path: ".agents/skills/", kind: "recorded", sharedWith: ["codex", "antigravity"] },
3134
3203
  ...SHARED_BY_ALL("opencode")
3135
3204
  ],
3136
3205
  antigravity: [
3137
- { path: ".agents/rules/uzys-harness.md", kind: "recorded", sharedWith: [] },
3138
- // #596 — 앵커뿐 아니라 **형제 룰 파일들**도 antigravity 소유다(transform 이 externalFiles 에
3139
- // 기록한다). 소유 표가 앵커 하나만 알아서 --cli antigravity 가 룰을 회수하지도 보고하지도
3140
- // 않았다(전량 uninstall 은 회수함 — 유일한 누수 경로).
3206
+ // ADR-103 — Antigravity 도 루트 `AGENTS.md` 를 앵커로 읽는다(옛 앵커 `.agents/rules/uzys-harness.md` 는 은퇴 — 은퇴 전
3207
+ // 설치본의 그 기록은 아래 접두 행이 덮는다).
3208
+ { path: "AGENTS.md", kind: "recorded", sharedWith: ["codex", "opencode"] },
3209
+ // #596 — 룰 파일들도 antigravity 소유다(transform 이 externalFiles 에 기록한다).
3141
3210
  { path: ".agents/rules/", kind: "recorded", sharedWith: [] },
3142
3211
  { path: ".agents/skills/", kind: "recorded", sharedWith: ["codex", "opencode"] },
3143
3212
  ...SHARED_BY_ALL("antigravity")
@@ -3670,15 +3739,95 @@ function legacyGitignoreSeed(text, render, previous) {
3670
3739
 
3671
3740
  // src/installer.ts
3672
3741
  init_esm_shims();
3673
- import { chmodSync as chmodSync3, existsSync as existsSync21, readdirSync as readdirSync3, readFileSync as readFileSync20, writeFileSync as writeFileSync8 } from "fs";
3674
- import { basename as basename5, join as join25, relative as relative3, resolve, sep as sep4 } from "path";
3742
+ import { chmodSync as chmodSync3, existsSync as existsSync21, readdirSync as readdirSync4, readFileSync as readFileSync22, writeFileSync as writeFileSync8 } from "fs";
3743
+ import { basename as basename4, join as join28, relative as relative3, resolve, sep as sep4 } from "path";
3675
3744
 
3676
3745
  // src/anchor-seed.ts
3677
3746
  init_esm_shims();
3678
3747
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
3679
3748
  import { join as join6 } from "path";
3680
- var SCAFFOLD_MARK = "SCAFFOLD \u2014 not filled in yet";
3681
- var TRACK_NOTE_MARK = "Active track(s):";
3749
+
3750
+ // src/agents-md-render.ts
3751
+ init_esm_shims();
3752
+ var AGENTS_MD_TEMPLATE = "templates/AGENTS.md.template";
3753
+ var AGENTS_MD_CLIS = ["codex", "opencode", "antigravity"];
3754
+ function isAgentsMdCli(cli) {
3755
+ return AGENTS_MD_CLIS.includes(cli);
3756
+ }
3757
+ function agentsMdClis(clis) {
3758
+ return [...new Set(clis.filter(isAgentsMdCli))].sort(
3759
+ (a, b2) => CLI_BASE_SORT_ORDER[a] - CLI_BASE_SORT_ORDER[b2]
3760
+ );
3761
+ }
3762
+ function renameSlashes(text) {
3763
+ return text.replaceAll("/uzys:", "/uzys-");
3764
+ }
3765
+ var RULES_NOTE = [
3766
+ "> \uD558\uB124\uC2A4\uAC00 \uC124\uCE58\uD55C \uB8F0\uC774 \uC5EC\uAE30 \uBCF8\uBB38\uC73C\uB85C \uB4E4\uC5B4\uC628\uB2E4. \uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C\uB9CC \uC9C0\uD0AC \uADDC\uCE59\uC744 \uC138\uC6B0\uBA74 \uC774 \uBB38\uC11C\uC5D0",
3767
+ "> \uC774\uC5B4 \uC4F0\uAC70\uB098, \uD558\uC704 \uB514\uB809\uD130\uB9AC\uC5D0 `AGENTS.md` \uB97C \uB450\uBA74 \uADF8 \uBC94\uC704\uC5D0\uC11C \uB354 \uAD6C\uCCB4\uC801\uC778 \uADDC\uCE59\uC774 \uB41C\uB2E4."
3768
+ ].join("\n");
3769
+ var CODEX_SIZE_NOTE = [
3770
+ ">",
3771
+ "> Codex \uB294 **\uD569\uCE5C \uD06C\uAE30**\uAC00 `project_doc_max_bytes`(\uAE30\uBCF8 32 KiB)\uC5D0 \uB2FF\uC73C\uBA74 **\uAC70\uAE30\uC11C \uC77D\uAE30\uB97C \uBA48\uCD98\uB2E4** \u2014 \uB4A4\uC5D0 \uC624\uB294",
3772
+ "> \uD30C\uC77C\uC774 \uC870\uC6A9\uD788 \uBE60\uC9C0\uACE0, \uD558\uC704 \uD30C\uC77C\uC774 \uB4A4\uC5D0 \uC628\uB2E4. \uB098\uB220 \uC4F0\uAE30 \uC804\uC5D0 \uCD1D\uB7C9\uC744 \uBA3C\uC800 \uBCF8\uB2E4."
3773
+ ].join("\n");
3774
+ var CODEX_SESSION_START = "`docs/SPEC.md` \uAC00 \uC788\uC73C\uBA74 \uC138\uC158 \uC2DC\uC791 \uB54C \uBA3C\uC800 \uC77D\uB294\uB2E4 \u2014 \uD604\uC7AC \uBC94\uC704\uC640 \uC644\uB8CC \uAE30\uC900\uC758 \uC575\uCEE4\uB2E4. \uC5C6\uC73C\uBA74 \uC774 \uC904\uC740 \uD574\uB2F9 \uC5C6\uB2E4.";
3775
+ var PROTECTED_LEAD = {
3776
+ codex: 'Codex `sandbox_mode = "workspace-write"` + `approval_policy = "on-request"` \uAC00 1\uCC28 \uBC29\uC5B4\uB2E4.',
3777
+ opencode: "OpenCode `permission` \uC124\uC815\uC774 1\uCC28 \uBC29\uC5B4\uB2E4."
3778
+ };
3779
+ function pruneTemplate(template, empty) {
3780
+ const lines2 = template.split("\n");
3781
+ const kept = [];
3782
+ for (let i = 0; i < lines2.length; i++) {
3783
+ const line = lines2[i] ?? "";
3784
+ if (empty.has(line.trim())) {
3785
+ if ((lines2[i + 1] ?? "").trim() === "") i++;
3786
+ continue;
3787
+ }
3788
+ kept.push(line);
3789
+ }
3790
+ const names = new Set([...template.matchAll(/^## (.+)$/gm)].map((m) => (m[1] ?? "").trim()));
3791
+ const isKnownHeading = (l) => {
3792
+ const name = /^## (.+?)\s*$/.exec(l)?.[1];
3793
+ return name !== void 0 && names.has(name);
3794
+ };
3795
+ const out = [];
3796
+ for (let i = 0; i < kept.length; i++) {
3797
+ const line = kept[i] ?? "";
3798
+ if (isKnownHeading(line)) {
3799
+ let j4 = i + 1;
3800
+ while (j4 < kept.length && !isKnownHeading(kept[j4] ?? "")) j4++;
3801
+ if (kept.slice(i + 1, j4).every((l) => l.trim() === "")) {
3802
+ i = j4 - 1;
3803
+ continue;
3804
+ }
3805
+ }
3806
+ out.push(line);
3807
+ }
3808
+ return out.join("\n");
3809
+ }
3810
+ function renderAgentsMd(params) {
3811
+ const clis = agentsMdClis(params.clis);
3812
+ if (clis.length === 0) throw new Error("renderAgentsMd: empty CLI set");
3813
+ const inlineRules = clis.includes("codex") || clis.includes("opencode");
3814
+ const leads = clis.flatMap((c3) => PROTECTED_LEAD[c3] ?? []);
3815
+ const values = {
3816
+ "{HARNESS_RULES_NOTE}": inlineRules ? clis.includes("codex") ? `${RULES_NOTE}
3817
+ ${CODEX_SIZE_NOTE}` : RULES_NOTE : "",
3818
+ "{HARNESS_RULES}": inlineRules ? params.harnessRules : "",
3819
+ "{SESSION_START}": clis.includes("codex") ? CODEX_SESSION_START : "",
3820
+ "{PROTECTED_FILES_LEAD}": leads.length === 0 ? "" : `${leads.join("\n")} \uADF8 \uC704\uC5D0:`
3821
+ };
3822
+ const empty = new Set(Object.keys(values).filter((k2) => (values[k2] ?? "").trim() === ""));
3823
+ const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
3824
+ let out = pruneTemplate(params.template, empty).replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{CLI_NAMES}", clis.map((c3) => CLI_DISPLAY_NAMES[c3]).join(" \xB7 "));
3825
+ for (const [k2, v2] of Object.entries(values)) out = out.replaceAll(k2, v2);
3826
+ out = out.replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext);
3827
+ return renameSlashes(out);
3828
+ }
3829
+
3830
+ // src/anchor-seed.ts
3682
3831
  function readIfExists(path) {
3683
3832
  if (!existsSync5(path)) return null;
3684
3833
  try {
@@ -3687,37 +3836,12 @@ function readIfExists(path) {
3687
3836
  return null;
3688
3837
  }
3689
3838
  }
3690
- function dropHarnessQuoteBlocks(lines2) {
3691
- const out = [];
3692
- let i = 0;
3693
- while (i < lines2.length) {
3694
- if (!(lines2[i] ?? "").trim().startsWith(">")) {
3695
- out.push(lines2[i] ?? "");
3696
- i += 1;
3697
- continue;
3698
- }
3699
- let end = i;
3700
- while (end < lines2.length && (lines2[end] ?? "").trim().startsWith(">")) end++;
3701
- const run = lines2.slice(i, end);
3702
- const text = run.join("\n");
3703
- if (!text.includes(SCAFFOLD_MARK) && !text.includes(TRACK_NOTE_MARK)) out.push(...run);
3704
- i = end;
3705
- }
3706
- return out;
3707
- }
3708
3839
  function dropTitle(lines2) {
3709
3840
  const out = [...lines2];
3710
3841
  while (out.length > 0 && (out[0] ?? "").trim() === "") out.shift();
3711
3842
  if (out.length > 0 && /^#\s/.test(out[0] ?? "")) out.shift();
3712
3843
  return out;
3713
3844
  }
3714
- function compact2(lines2) {
3715
- return lines2.map((l) => l.replace(/\r$/, "").trimEnd()).filter((l) => l.trim() !== "");
3716
- }
3717
- function isPristineScaffold(body, surface) {
3718
- const pristine = compact2(dropHarnessQuoteBlocks(renderFillScaffold(surface).split("\n")));
3719
- return compact2(body).join("\n") === pristine.join("\n");
3720
- }
3721
3845
  function compose(body, into) {
3722
3846
  const trimmed = [...body];
3723
3847
  while (trimmed.length > 0 && (trimmed.at(-1) ?? "").trim() === "") trimmed.pop();
@@ -3732,18 +3856,18 @@ function seedAgentsMdProjectContext(projectDir) {
3732
3856
  if (root === null) return null;
3733
3857
  const withoutImport = stripHarnessImport(root) ?? root;
3734
3858
  const body = dropHarnessQuoteBlocks(dropTitle(withoutImport.split("\n")));
3735
- if (isPristineScaffold(body, "claude")) return null;
3859
+ if (isUnfilledScaffold(body)) return null;
3736
3860
  return compose(body, "agents-md");
3737
3861
  }
3738
3862
  function seedRootClaudeProjectContext(projectDir, harnessRoot) {
3739
3863
  const agents = readIfExists(join6(projectDir, "AGENTS.md"));
3740
3864
  if (agents === null) return null;
3741
- const template = readIfExists(join6(harnessRoot, "templates/codex/AGENTS.md.template")) ?? readIfExists(join6(harnessRoot, "templates/opencode/AGENTS.md.template"));
3865
+ const template = readIfExists(join6(harnessRoot, AGENTS_MD_TEMPLATE));
3742
3866
  if (template === null) return null;
3743
3867
  const installer = projectContextInstallerLines({ existing: agents, template });
3744
3868
  if (installer === null) return null;
3745
3869
  const body = dropHarnessQuoteBlocks(installer);
3746
- if (isPristineScaffold(body, "agents-md")) return null;
3870
+ if (isUnfilledScaffold(body)) return null;
3747
3871
  return compose(body, "claude");
3748
3872
  }
3749
3873
 
@@ -3787,10 +3911,10 @@ function installCiScaffold(ctx) {
3787
3911
 
3788
3912
  // src/cli-transforms.ts
3789
3913
  init_esm_shims();
3790
- import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
3791
- import { join as join17 } from "path";
3914
+ import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
3915
+ import { join as join20 } from "path";
3792
3916
 
3793
- // src/antigravity/transform.ts
3917
+ // src/agents-md-transform.ts
3794
3918
  init_esm_shims();
3795
3919
  import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
3796
3920
  import { basename, join as join12 } from "path";
@@ -3831,99 +3955,26 @@ function agentsSkillSlot(params) {
3831
3955
  };
3832
3956
  }
3833
3957
 
3834
- // src/codex/agents-md.ts
3835
- init_esm_shims();
3836
- function renameSlashes(text) {
3837
- return text.replaceAll("/uzys:", "/uzys-");
3838
- }
3839
- function renderAgentsMd(params) {
3840
- const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
3841
- const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
3842
- return renameSlashes(replaced);
3843
- }
3844
-
3845
- // src/codex/skills.ts
3846
- init_esm_shims();
3847
- import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
3848
- import { join as join9 } from "path";
3849
- function renderBundledSkill(source) {
3850
- const trimmed = source.trimEnd();
3851
- const lines2 = trimmed.split(/\r?\n/);
3852
- if (lines2[0] !== "---") {
3853
- return `${portBody(trimmed)}
3854
- `;
3855
- }
3856
- let secondDelimAt = -1;
3857
- for (let i = 1; i < lines2.length; i++) {
3858
- if (lines2[i] === "---") {
3859
- secondDelimAt = i;
3860
- break;
3861
- }
3862
- }
3863
- if (secondDelimAt < 0) {
3864
- return `${portBody(trimmed)}
3865
- `;
3866
- }
3867
- const frontmatter = lines2.slice(0, secondDelimAt + 1).join("\n");
3868
- const body = lines2.slice(secondDelimAt + 1).join("\n");
3869
- return `${frontmatter}
3870
- ${portBody(body)}
3871
- `;
3872
- }
3873
- function portBody(body) {
3874
- return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
3875
- }
3876
- function writeBundledSkillDirs(params) {
3877
- const { harnessRoot, projectDir, skillIds, writer } = params;
3878
- const written = [];
3879
- for (const id of skillIds) {
3880
- const srcDir = join9(harnessRoot, "templates/skills", id);
3881
- const srcSkill = join9(srcDir, "SKILL.md");
3882
- if (!existsSync7(srcSkill)) {
3883
- continue;
3884
- }
3885
- const targetDir = join9(projectDir, ".agents", "skills", id);
3886
- const targetSkill = join9(targetDir, "SKILL.md");
3887
- if (!writer.write(targetSkill, renderBundledSkill(readFileSync5(srcSkill, "utf8")))) {
3888
- continue;
3889
- }
3890
- written.push(targetSkill);
3891
- for (const rel of listFilesRecursive(srcDir)) {
3892
- if (rel === "SKILL.md") continue;
3893
- if (rel.split("/").some((segment2) => segment2.startsWith("."))) continue;
3894
- const segments = rel.split("/");
3895
- const raw = readFileSync5(join9(srcDir, ...segments), "utf8");
3896
- const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
3897
- ` : raw;
3898
- const target = join9(targetDir, ...segments);
3899
- if (!writer.write(target, ported, { createInRefresh: true })) continue;
3900
- if (rel.endsWith(".sh")) chmodSync(target, 493);
3901
- written.push(target);
3902
- }
3903
- }
3904
- return written;
3905
- }
3906
-
3907
3958
  // src/owned-write.ts
3908
3959
  init_esm_shims();
3909
- import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3960
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
3910
3961
  import { dirname as dirname2, relative as relative2, sep as sep3 } from "path";
3911
3962
 
3912
3963
  // src/foreign-slot.ts
3913
3964
  init_esm_shims();
3914
3965
  import { lstatSync, realpathSync, statSync } from "fs";
3915
- import { join as join10 } from "path";
3966
+ import { join as join9 } from "path";
3916
3967
  var SKILL_SLOT = /^((?:\.claude|\.agents)\/skills\/[^/]+)(?:\/|$)/;
3917
3968
  function occupiedByNonDirectory(path) {
3918
3969
  const stat = lstatSync(path, { throwIfNoEntry: false });
3919
3970
  return stat !== void 0 && !stat.isDirectory();
3920
3971
  }
3921
3972
  function linksToProjectSharedSkill(projectDir, id) {
3922
- const slot = join10(projectDir, ".claude/skills", id);
3973
+ const slot = join9(projectDir, ".claude/skills", id);
3923
3974
  if (lstatSync(slot, { throwIfNoEntry: false })?.isSymbolicLink() !== true) return false;
3924
3975
  try {
3925
3976
  const real = realpathSync(slot);
3926
- return real === join10(realpathSync(projectDir), ".agents/skills", id) && statSync(real).isDirectory();
3977
+ return real === join9(realpathSync(projectDir), ".agents/skills", id) && statSync(real).isDirectory();
3927
3978
  } catch {
3928
3979
  return false;
3929
3980
  }
@@ -3931,15 +3982,15 @@ function linksToProjectSharedSkill(projectDir, id) {
3931
3982
  function foreignOwnedTarget(projectDir, rel) {
3932
3983
  const slot = SKILL_SLOT.exec(rel)?.[1];
3933
3984
  if (slot === void 0) return null;
3934
- if (occupiedByNonDirectory(join10(projectDir, slot))) return slot;
3985
+ if (occupiedByNonDirectory(join9(projectDir, slot))) return slot;
3935
3986
  if (rel !== slot) {
3936
3987
  const parts = rel.slice(slot.length + 1).split("/");
3937
3988
  let cursor = slot;
3938
3989
  for (const part of parts.slice(0, -1)) {
3939
3990
  cursor = `${cursor}/${part}`;
3940
- if (occupiedByNonDirectory(join10(projectDir, cursor))) return cursor;
3991
+ if (occupiedByNonDirectory(join9(projectDir, cursor))) return cursor;
3941
3992
  }
3942
- const leaf = lstatSync(join10(projectDir, rel), { throwIfNoEntry: false });
3993
+ const leaf = lstatSync(join9(projectDir, rel), { throwIfNoEntry: false });
3943
3994
  if (leaf !== void 0 && !leaf.isFile()) return rel;
3944
3995
  }
3945
3996
  return null;
@@ -3965,7 +4016,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
3965
4016
  }
3966
4017
  if (outside.skip(absPath)) return false;
3967
4018
  const digest = hashContent(content);
3968
- if (!existsSync8(absPath)) {
4019
+ if (!existsSync7(absPath)) {
3969
4020
  const recordedPath = baseline.has(rel);
3970
4021
  if (refreshOnly && !opts?.createInRefresh && !recordedPath) return false;
3971
4022
  mkdirSync2(dirname2(absPath), { recursive: true });
@@ -3973,7 +4024,7 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
3973
4024
  updated++;
3974
4025
  if (refreshOnly && recordedPath) restored.push(rel);
3975
4026
  } else {
3976
- const current = readFileSync6(absPath, "utf8");
4027
+ const current = readFileSync5(absPath, "utf8");
3977
4028
  if (current !== content) {
3978
4029
  if (!isHarnessOwned(baseline, rel, current)) {
3979
4030
  const backup = backupFile(absPath, now);
@@ -4007,8 +4058,26 @@ function createOwnedWriter(projectDir, baseline, options = {}) {
4007
4058
 
4008
4059
  // src/rules-port.ts
4009
4060
  init_esm_shims();
4010
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
4011
- import { join as join11 } from "path";
4061
+ import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync6 } from "fs";
4062
+ import { join as join10 } from "path";
4063
+ function claudePaths(source) {
4064
+ if (!source.startsWith("---\n")) return [];
4065
+ const end = source.indexOf("\n---", 4);
4066
+ if (end === -1) return [];
4067
+ const lines2 = source.slice(4, end).split("\n");
4068
+ const unquote = (v2) => v2.trim().replace(/^(["'])(.*)\1$/, "$2");
4069
+ const at5 = lines2.findIndex((l) => /^paths:/.test(l));
4070
+ if (at5 === -1) return [];
4071
+ const inline = (lines2[at5] ?? "").slice("paths:".length).trim();
4072
+ if (inline !== "") return [unquote(inline)].filter((p2) => p2 !== "");
4073
+ const out = [];
4074
+ for (const l of lines2.slice(at5 + 1)) {
4075
+ const m = /^\s+-\s+(.+)$/.exec(l);
4076
+ if (m === null) break;
4077
+ out.push(unquote(m[1] ?? ""));
4078
+ }
4079
+ return out.filter((p2) => p2 !== "");
4080
+ }
4012
4081
  function stripClaudeFrontmatter(source) {
4013
4082
  if (!source.startsWith("---\n")) return source.trim();
4014
4083
  const end = source.indexOf("\n---", 4);
@@ -4018,156 +4087,31 @@ function stripClaudeFrontmatter(source) {
4018
4087
  function portRules(harnessRoot, ruleNames) {
4019
4088
  const ported = [];
4020
4089
  for (const name of ruleNames) {
4021
- const src = join11(harnessRoot, "templates/rules", `${name}.md`);
4022
- if (!existsSync9(src)) continue;
4023
- ported.push({ name, body: stripClaudeFrontmatter(readFileSync7(src, "utf8")) });
4090
+ const src = join10(harnessRoot, "templates/rules", `${name}.md`);
4091
+ if (!existsSync8(src)) continue;
4092
+ const source = readFileSync6(src, "utf8");
4093
+ ported.push({ name, body: stripClaudeFrontmatter(source), paths: claudePaths(source) });
4024
4094
  }
4025
4095
  return ported;
4026
4096
  }
4097
+ function ruleHeadings(harnessRoot) {
4098
+ const dir = join10(harnessRoot, "templates/rules");
4099
+ if (!existsSync8(dir)) return /* @__PURE__ */ new Set();
4100
+ const names = readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3));
4101
+ const block = renderRulesBlock(portRules(harnessRoot, names));
4102
+ return new Set(
4103
+ block.split("\n").filter((l) => /^## /.test(l)).map((l) => l.trim())
4104
+ );
4105
+ }
4027
4106
  function renderRulesBlock(rules) {
4028
4107
  if (rules.length === 0) return "";
4029
4108
  return rules.map((r) => r.body.replace(/^#\s+/, "## ")).join("\n\n---\n\n");
4030
4109
  }
4031
4110
 
4032
- // src/antigravity/transform.ts
4033
- function runAntigravityTransform(params) {
4034
- const {
4035
- harnessRoot,
4036
- projectDir,
4037
- selectedInternalSkills = [],
4038
- rules = [],
4039
- baseline,
4040
- refreshOnly
4041
- } = params;
4042
- const writer = createOwnedWriter(projectDir, baseline, {
4043
- refreshOnly: refreshOnly ?? false,
4044
- ...params.journal === void 0 ? {} : { journal: params.journal }
4045
- });
4046
- const { skillIds, skillWriter } = agentsSkillSlot({
4047
- projectDir,
4048
- selectedInternalSkills,
4049
- refreshOnly: refreshOnly ?? false,
4050
- writer
4051
- });
4052
- const installedByRecord = params.installedByRecord === true;
4053
- const rulesFile = writeRules(harnessRoot, projectDir, writer, skillIds, installedByRecord);
4054
- const antigravityInstalled = installedByRecord || rulesFile !== null;
4055
- const harnessRuleFiles = [];
4056
- for (const rule of portRules(harnessRoot, rules)) {
4057
- const target = join12(projectDir, ".agents", "rules", `${rule.name}.md`);
4058
- if (!writer.write(target, `${rule.body}
4059
- `, { createInRefresh: antigravityInstalled })) {
4060
- continue;
4061
- }
4062
- harnessRuleFiles.push(target);
4063
- }
4064
- const skillFiles = writeBundledSkillDirs({
4065
- harnessRoot,
4066
- projectDir,
4067
- skillIds,
4068
- writer: skillWriter
4069
- });
4070
- return {
4071
- rulesFile,
4072
- harnessRuleFiles,
4073
- skillFiles,
4074
- ownership: writer.result()
4075
- };
4076
- }
4077
- function writeRules(harnessRoot, projectDir, writer, selectedInternalSkills, createInRefresh) {
4078
- const claudeMdPath = join12(harnessRoot, "templates/CLAUDE.md");
4079
- const templatePath = join12(harnessRoot, "templates/antigravity/AGENTS.md.template");
4080
- if (!existsSync10(claudeMdPath) || !existsSync10(templatePath)) {
4081
- return null;
4082
- }
4083
- const claudeMd = readFileSync8(claudeMdPath, "utf8");
4084
- const template = readFileSync8(templatePath, "utf8");
4085
- const target = join12(projectDir, ".agents", "rules", "uzys-harness.md");
4086
- const rulesOut = renderAgentsMd({
4087
- template,
4088
- claudeMd,
4089
- projectName: basename(projectDir),
4090
- // ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
4091
- projectContext: withContinuousSkillsNote(
4092
- renderFillScaffold("antigravity-rule"),
4093
- selectedInternalSkills
4094
- )
4095
- });
4096
- const withoutMarkers = rulesOut.split("\n").filter((l) => !/^<!-- uzys-harness:(?:anchor|skills):(?:start|end) -->$/.test(l.trim())).join("\n");
4097
- return writer.write(target, withoutMarkers, { createInRefresh }) ? target : null;
4098
- }
4099
-
4100
- // src/codex/opt-in.ts
4101
- init_esm_shims();
4102
- import { homedir } from "os";
4103
- import { join as join13 } from "path";
4104
-
4105
- // src/codex/trust-entry.ts
4106
- init_esm_shims();
4107
- import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
4108
- import { dirname as dirname3 } from "path";
4109
- var TRUST_BLOCK_REGEX = /\[projects\."([^"]+)"\]/g;
4110
- function registerTrustEntry(opts) {
4111
- const { configPath, projectDir } = opts;
4112
- try {
4113
- const existing = existsSync11(configPath) ? readFileSync9(configPath, "utf8") : "";
4114
- try {
4115
- parse(existing);
4116
- } catch (e2) {
4117
- return {
4118
- status: "unreadable",
4119
- message: `not valid TOML (${firstLine(e2)}) \u2014 left untouched`
4120
- };
4121
- }
4122
- if (hasTrustEntry(existing, projectDir)) {
4123
- return { status: "already-present" };
4124
- }
4125
- mkdirSync3(dirname3(configPath), { recursive: true });
4126
- const block = `
4127
- [projects."${projectDir}"]
4128
- trust_level = "trusted"
4129
- `;
4130
- writeFileSync5(configPath, existing + block);
4131
- return { status: "registered" };
4132
- } catch (e2) {
4133
- return {
4134
- status: "error",
4135
- message: e2 instanceof Error ? e2.message : String(e2)
4136
- };
4137
- }
4138
- }
4139
- function hasTrustEntry(configContent, projectDir) {
4140
- const matches = [...configContent.matchAll(TRUST_BLOCK_REGEX)].map((m) => m[1]);
4141
- return matches.includes(projectDir);
4142
- }
4143
- function firstLine(e2) {
4144
- return (e2 instanceof Error ? e2.message : String(e2)).split("\n")[0] ?? "";
4145
- }
4146
-
4147
- // src/codex/opt-in.ts
4148
- function runCodexOptIn(ctx) {
4149
- const codexHome = ctx.codexHome ?? join13(homedir(), ".codex");
4150
- const configPath = join13(codexHome, "config.toml");
4151
- const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
4152
- return {
4153
- trustEntry: {
4154
- enabled: true,
4155
- status: result.status,
4156
- configPath,
4157
- ...result.message ? { message: result.message } : {}
4158
- }
4159
- };
4160
- }
4161
-
4162
- // src/codex/transform.ts
4163
- init_esm_shims();
4164
- import { chmodSync as chmodSync2, existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
4165
- import { basename as basename2, join as join15 } from "path";
4166
-
4167
4111
  // src/shared-write.ts
4168
4112
  init_esm_shims();
4169
- import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync10, rmSync, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
4170
- import { dirname as dirname4, join as join14 } from "path";
4113
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
4114
+ import { dirname as dirname3, join as join11 } from "path";
4171
4115
  function toPortions(path, portions) {
4172
4116
  const adapter = adapterFor(path);
4173
4117
  if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
@@ -4182,8 +4126,8 @@ function writeShared(params) {
4182
4126
  const { projectDir, path, render, record, baseline, writer, refreshOnly } = params;
4183
4127
  const adapter = adapterFor(path);
4184
4128
  if (adapter === null) throw new Error(`shared-write: ${path} is not a shared file`);
4185
- const abs = join14(projectDir, path);
4186
- const onDisk = existsSync12(abs) ? readFileSync10(abs, "utf8") : null;
4129
+ const abs = join11(projectDir, path);
4130
+ const onDisk = existsSync9(abs) ? readFileSync7(abs, "utf8") : null;
4187
4131
  const recorded2 = recordedFor(path, record);
4188
4132
  const excluded = excludedKeys(path, record.excluded ?? []);
4189
4133
  const result = (action2, rest = {}) => ({
@@ -4258,13 +4202,13 @@ function writeShared(params) {
4258
4202
  );
4259
4203
  const ownCreated = params.ownCreated !== false;
4260
4204
  if (onDisk === null && !ownCreated) {
4261
- mkdirSync4(dirname4(abs), { recursive: true });
4262
- writeFileSync6(abs, upserted.text);
4205
+ mkdirSync3(dirname3(abs), { recursive: true });
4206
+ writeFileSync5(abs, upserted.text);
4263
4207
  } else if (harnessMade) {
4264
4208
  writer.write(abs, upserted.text, { createInRefresh: true });
4265
4209
  } else if (upserted.changed) {
4266
- mkdirSync4(dirname4(abs), { recursive: true });
4267
- writeFileSync6(abs, upserted.text);
4210
+ mkdirSync3(dirname3(abs), { recursive: true });
4211
+ writeFileSync5(abs, upserted.text);
4268
4212
  }
4269
4213
  writer.journal?.portions(path, portions);
4270
4214
  const action = onDisk === null ? "created" : upserted.text !== onDisk ? "updated" : "unchanged";
@@ -4302,13 +4246,20 @@ function agentsMdModel(disk, recordedByHarness, recordedBlock = false) {
4302
4246
  return "block";
4303
4247
  }
4304
4248
  function writeAgentsMd(params) {
4305
- const abs = join14(params.projectDir, AGENTS_MD);
4306
- const disk = existsSync12(abs) ? readFileSync10(abs, "utf8") : null;
4249
+ const abs = join11(params.projectDir, AGENTS_MD);
4250
+ const disk = existsSync9(abs) ? readFileSync7(abs, "utf8") : null;
4307
4251
  const recordedBlock = (params.record.portions ?? []).some((p2) => p2.path === AGENTS_MD);
4308
4252
  if (agentsMdModel(disk, params.baseline.has(AGENTS_MD), recordedBlock) === "sections") {
4309
4253
  params.writer.write(
4310
4254
  abs,
4311
- mergeAgentsMd({ rendered: params.rendered, existing: disk, template: params.template })
4255
+ mergeAgentsMd({
4256
+ rendered: params.rendered,
4257
+ existing: disk,
4258
+ template: params.template,
4259
+ ...params.replaceUnfilledContext === true ? { replaceUnfilledContext: true } : {},
4260
+ ...params.harnessRuleHeadings === void 0 ? {} : { harnessRuleHeadings: params.harnessRuleHeadings }
4261
+ }),
4262
+ params.createInRefresh === true ? { createInRefresh: true } : void 0
4312
4263
  );
4313
4264
  return { model: "sections", shared: null };
4314
4265
  }
@@ -4349,9 +4300,9 @@ function stripShared(params) {
4349
4300
  portions: [],
4350
4301
  ...rest
4351
4302
  });
4352
- const abs = join14(projectDir, path);
4353
- if (!existsSync12(abs) || !statSync2(abs).isFile()) return out("none");
4354
- const disk = readFileSync10(abs, "utf8");
4303
+ const abs = join11(projectDir, path);
4304
+ if (!existsSync9(abs) || !statSync2(abs).isFile()) return out("none");
4305
+ const disk = readFileSync7(abs, "utf8");
4355
4306
  const recorded2 = new Map(
4356
4307
  params.portions.filter((p2) => p2.path === path).map((p2) => [p2.key, p2.sha256])
4357
4308
  );
@@ -4363,39 +4314,444 @@ function stripShared(params) {
4363
4314
  next: null,
4364
4315
  adapter
4365
4316
  });
4366
- if (verdict.verdict === "leave+advise") {
4367
- return out("left", { line: verdict.line, portions: toPortions(path, recorded2) });
4317
+ if (verdict.verdict === "leave+advise") {
4318
+ return out("left", { line: verdict.line, portions: toPortions(path, recorded2) });
4319
+ }
4320
+ if (recorded2.size === 0) {
4321
+ const names = params.remnant(disk);
4322
+ return names.length === 0 ? out("none") : out("left", { line: params.remnantLine(names) });
4323
+ }
4324
+ const impl = ADAPTERS[adapter];
4325
+ const res = impl.strip(disk, {
4326
+ recorded: recorded2,
4327
+ excluded: excludedKeys(path, params.excluded),
4328
+ projectDir
4329
+ });
4330
+ if (!res.ok) {
4331
+ return out("left", {
4332
+ line: `could not read it (${res.reason}) \u2014 harness part not removed`,
4333
+ portions: toPortions(path, recorded2)
4334
+ });
4335
+ }
4336
+ const fileRemoved = params.removeIfEmpty === true && res.removed.length > 0 && res.empty;
4337
+ if (params.write && fileRemoved) rmSync(abs);
4338
+ else if (params.write && res.changed) writeFileSync5(abs, res.text);
4339
+ const action = res.removed.length > 0 ? "removed" : res.kept.length > 0 ? "left" : "none";
4340
+ return out(action, {
4341
+ removed: res.removed,
4342
+ kept: res.kept,
4343
+ portions: toPortions(path, res.portions),
4344
+ ...fileRemoved ? { fileRemoved } : {}
4345
+ });
4346
+ }
4347
+
4348
+ // src/agents-md-transform.ts
4349
+ var LEGACY_ANTIGRAVITY_ANCHOR = ".agents/rules/uzys-harness.md";
4350
+ function runAgentsMdTransform(params) {
4351
+ const { harnessRoot, projectDir, baseline, shared: shared2 = {} } = params;
4352
+ const refreshOnly = params.refreshOnly === true;
4353
+ const clis = agentsMdClis(params.clis);
4354
+ const writer = createOwnedWriter(projectDir, baseline, {
4355
+ refreshOnly,
4356
+ ...params.journal === void 0 ? {} : { journal: params.journal }
4357
+ });
4358
+ const claudeMd = readRequired(join12(harnessRoot, "templates/CLAUDE.md"));
4359
+ const template = readRequired(join12(harnessRoot, AGENTS_MD_TEMPLATE));
4360
+ const { skillIds } = agentsSkillSlot({
4361
+ projectDir,
4362
+ selectedInternalSkills: params.selectedInternalSkills,
4363
+ refreshOnly,
4364
+ writer
4365
+ });
4366
+ const agentsMdPath = join12(projectDir, "AGENTS.md");
4367
+ const exists = existsSync10(agentsMdPath);
4368
+ const creates = !exists && (!refreshOnly || params.installedByRecord === true);
4369
+ const legacy = params.legacyContext ?? null;
4370
+ const fromRoot = creates ? seedAgentsMdProjectContext(projectDir) : null;
4371
+ const seed = fromRoot ?? legacy;
4372
+ const seededFrom = fromRoot !== null ? "CLAUDE.md" : legacy !== null ? LEGACY_ANTIGRAVITY_ANCHOR : null;
4373
+ ensureDir(projectDir);
4374
+ const rendered = renderAgentsMd({
4375
+ template,
4376
+ claudeMd,
4377
+ projectName: basename(projectDir),
4378
+ // ADR-085 — 상시 스킬 안내는 앵커가 아니라 프로젝트 맥락에, 깔린 것만(#503 — 설치자 절 안이라 마커로 감싼다).
4379
+ projectContext: withMarkedContinuousSkillsNote(
4380
+ seed ?? renderFillScaffold("agents-md"),
4381
+ skillIds
4382
+ ),
4383
+ harnessRules: renderRulesBlock(portRules(harnessRoot, params.rules)),
4384
+ clis
4385
+ });
4386
+ const diskBefore = exists ? readFileSync8(agentsMdPath, "utf8") : null;
4387
+ const agentsMd = writeAgentsMd({
4388
+ projectDir,
4389
+ rendered,
4390
+ template,
4391
+ anchorTitle: anchorTitle(claudeMd),
4392
+ writer,
4393
+ baseline,
4394
+ record: shared2,
4395
+ refreshOnly,
4396
+ ...params.installedByRecord === true ? { createInRefresh: true } : {},
4397
+ ...legacy !== null && fromRoot === null ? { replaceUnfilledContext: true } : {},
4398
+ harnessRuleHeadings: ruleHeadings(harnessRoot)
4399
+ });
4400
+ return {
4401
+ agentsMdPath,
4402
+ agentsMd,
4403
+ agentsMdSeededFrom: seededFrom === null ? null : seededInto(agentsMdPath, diskBefore, seed, seededFrom),
4404
+ clis,
4405
+ ownership: writer.result()
4406
+ };
4407
+ }
4408
+ function seededInto(path, before, seed, from) {
4409
+ if (seed === null || !existsSync10(path)) return null;
4410
+ const after = readFileSync8(path, "utf8");
4411
+ const body = renameSlashes(seed.slice(seed.indexOf("\n\n") + 2));
4412
+ return after !== before && after.includes(body) ? from : null;
4413
+ }
4414
+ function readRequired(path) {
4415
+ if (!existsSync10(path)) {
4416
+ throw new Error(`AGENTS.md transform: required source missing: ${path}`);
4417
+ }
4418
+ return readFileSync8(path, "utf8");
4419
+ }
4420
+
4421
+ // src/antigravity/retire-anchor.ts
4422
+ init_esm_shims();
4423
+ import { lstatSync as lstatSync2, readFileSync as readFileSync9, rmSync as rmSync2 } from "fs";
4424
+ import { join as join13 } from "path";
4425
+ var LEGACY_ANTIGRAVITY_ANCHOR_SECTIONS = ["Project Context", "Project Rules", "Protected Files"];
4426
+ function anchorRecord(previous) {
4427
+ return previous.find((f) => f.path === LEGACY_ANTIGRAVITY_ANCHOR) ?? null;
4428
+ }
4429
+ function readRegular(abs) {
4430
+ const stat = lstatSync2(abs, { throwIfNoEntry: false });
4431
+ if (stat === void 0 || !stat.isFile()) return null;
4432
+ try {
4433
+ return readFileSync9(abs, "utf8");
4434
+ } catch {
4435
+ return null;
4436
+ }
4437
+ }
4438
+ function legacyContextLines(text) {
4439
+ const lines2 = text.split("\n");
4440
+ const heading = (l) => {
4441
+ const name = /^## (.+?)\s*$/.exec(l.replace(/\r$/, ""))?.[1];
4442
+ return name !== void 0 && LEGACY_ANTIGRAVITY_ANCHOR_SECTIONS.includes(name) ? name : null;
4443
+ };
4444
+ const start = lines2.findIndex((l) => heading(l) === "Project Context");
4445
+ if (start === -1) return null;
4446
+ const rest = lines2.slice(start + 1);
4447
+ const end = rest.findIndex((l) => heading(l) !== null);
4448
+ return end === -1 ? rest : rest.slice(0, end);
4449
+ }
4450
+ function readLegacyAntigravityContext(projectDir, previous) {
4451
+ if (anchorRecord(previous) === null) return null;
4452
+ const text = readRegular(join13(projectDir, LEGACY_ANTIGRAVITY_ANCHOR));
4453
+ if (text === null) return null;
4454
+ const lines2 = legacyContextLines(text);
4455
+ if (lines2 === null) return null;
4456
+ const body = dropContinuousSkillsNote(dropHarnessQuoteBlocks(lines2));
4457
+ if (isUnfilledScaffold(body)) return null;
4458
+ return compose(currentFillPrompts(body), "agents-md");
4459
+ }
4460
+ function currentFillPrompts(body) {
4461
+ const fillId = (l) => /^<!-- FILL:([a-z-]+) — .*-->$/.exec(l.trim())?.[1] ?? null;
4462
+ const current = /* @__PURE__ */ new Map();
4463
+ for (const l of renderFillScaffold("agents-md").split("\n")) {
4464
+ const id = fillId(l);
4465
+ if (id !== null) current.set(id, l);
4466
+ }
4467
+ return body.map((l) => {
4468
+ const id = fillId(l);
4469
+ return id === null ? l : current.get(id) ?? l;
4470
+ });
4471
+ }
4472
+ function retireAntigravityAnchor(args) {
4473
+ const rec = anchorRecord(args.previous);
4474
+ if (rec === null) return null;
4475
+ const path = LEGACY_ANTIGRAVITY_ANCHOR;
4476
+ const abs = join13(args.projectDir, path);
4477
+ const left = (why) => ({
4478
+ path,
4479
+ action: "left",
4480
+ backup: null,
4481
+ forget: false,
4482
+ line: `left \u2014 ${why}; Antigravity reads AGENTS.md now, so this file can go`
4483
+ });
4484
+ if (args.outside.skip(abs)) return left("it points outside this project");
4485
+ const stat = lstatSync2(abs, { throwIfNoEntry: false });
4486
+ if (stat !== void 0 && !stat.isFile()) return left("it is a link or folder, so it is yours");
4487
+ const disk = stat === void 0 ? null : readFileSync9(abs, "utf8");
4488
+ const verdict = judge({
4489
+ op: "remove",
4490
+ kind: "harness",
4491
+ rec: { state: "sha", sha256: rec.sha256 },
4492
+ disk,
4493
+ next: null
4494
+ });
4495
+ if (disk === null) {
4496
+ return { path, action: "gone", backup: null, forget: true, line: "" };
4497
+ }
4498
+ const keepCopy = verdict.verdict === "backup+remove" || args.hadContent && !args.moved && verdict.verdict === "remove";
4499
+ if (verdict.verdict !== "remove" && verdict.verdict !== "backup+remove") {
4500
+ return left("could not judge it");
4501
+ }
4502
+ const backup = keepCopy ? backupFile(abs, args.now) : null;
4503
+ rmSync2(abs);
4504
+ const saved = backup === null ? "" : relativeBackup(args.projectDir, backup);
4505
+ const line = args.moved ? `retired \u2014 Antigravity reads AGENTS.md; your Project Context moved there${saved ? ` \xB7 previous file saved as ${saved}` : ""}` : args.hadContent ? `retired \u2014 AGENTS.md already has its own Project Context, so nothing was merged; your filled text is in ${saved}` : `retired \u2014 Antigravity reads AGENTS.md${saved ? ` \xB7 previous file saved as ${saved}` : ""}`;
4506
+ return { path, action: "removed", backup, forget: true, line };
4507
+ }
4508
+ function relativeBackup(projectDir, backup) {
4509
+ return backup.startsWith(`${projectDir}/`) ? backup.slice(projectDir.length + 1) : backup;
4510
+ }
4511
+
4512
+ // src/antigravity/transform.ts
4513
+ init_esm_shims();
4514
+ import { join as join15 } from "path";
4515
+
4516
+ // src/codex/skills.ts
4517
+ init_esm_shims();
4518
+ import { chmodSync, existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
4519
+ import { join as join14 } from "path";
4520
+ function renderBundledSkill(source) {
4521
+ const trimmed = source.trimEnd();
4522
+ const lines2 = trimmed.split(/\r?\n/);
4523
+ if (lines2[0] !== "---") {
4524
+ return `${portBody(trimmed)}
4525
+ `;
4526
+ }
4527
+ let secondDelimAt = -1;
4528
+ for (let i = 1; i < lines2.length; i++) {
4529
+ if (lines2[i] === "---") {
4530
+ secondDelimAt = i;
4531
+ break;
4532
+ }
4533
+ }
4534
+ if (secondDelimAt < 0) {
4535
+ return `${portBody(trimmed)}
4536
+ `;
4537
+ }
4538
+ const frontmatter = lines2.slice(0, secondDelimAt + 1).join("\n");
4539
+ const body = lines2.slice(secondDelimAt + 1).join("\n");
4540
+ return `${frontmatter}
4541
+ ${portBody(body)}
4542
+ `;
4543
+ }
4544
+ function portBody(body) {
4545
+ return renameSlashes(body).replace(/CLAUDE_PROJECT_DIR/g, "CODEX_PROJECT_DIR").trimEnd();
4546
+ }
4547
+ function writeBundledSkillDirs(params) {
4548
+ const { harnessRoot, projectDir, skillIds, writer } = params;
4549
+ const written = [];
4550
+ for (const id of skillIds) {
4551
+ const srcDir = join14(harnessRoot, "templates/skills", id);
4552
+ const srcSkill = join14(srcDir, "SKILL.md");
4553
+ if (!existsSync11(srcSkill)) {
4554
+ continue;
4555
+ }
4556
+ const targetDir = join14(projectDir, ".agents", "skills", id);
4557
+ const targetSkill = join14(targetDir, "SKILL.md");
4558
+ if (!writer.write(targetSkill, renderBundledSkill(readFileSync10(srcSkill, "utf8")))) {
4559
+ continue;
4560
+ }
4561
+ written.push(targetSkill);
4562
+ for (const rel of listFilesRecursive(srcDir)) {
4563
+ if (rel === "SKILL.md") continue;
4564
+ if (rel.split("/").some((segment2) => segment2.startsWith("."))) continue;
4565
+ const segments = rel.split("/");
4566
+ const raw = readFileSync10(join14(srcDir, ...segments), "utf8");
4567
+ const ported = rel.endsWith(".md") || rel.endsWith(".sh") ? `${portBody(raw)}
4568
+ ` : raw;
4569
+ const target = join14(targetDir, ...segments);
4570
+ if (!writer.write(target, ported, { createInRefresh: true })) continue;
4571
+ if (rel.endsWith(".sh")) chmodSync(target, 493);
4572
+ written.push(target);
4573
+ }
4574
+ }
4575
+ return written;
4576
+ }
4577
+
4578
+ // src/antigravity/transform.ts
4579
+ function antigravityFrontmatter(rule) {
4580
+ if (rule.paths.length === 0) return "---\ntrigger: always_on\n---\n\n";
4581
+ return `---
4582
+ trigger: glob
4583
+ globs: "${rule.paths.join(", ")}"
4584
+ ---
4585
+
4586
+ `;
4587
+ }
4588
+ function runAntigravityTransform(params) {
4589
+ const {
4590
+ harnessRoot,
4591
+ projectDir,
4592
+ selectedInternalSkills = [],
4593
+ rules = [],
4594
+ baseline,
4595
+ refreshOnly
4596
+ } = params;
4597
+ const writer = createOwnedWriter(projectDir, baseline, {
4598
+ refreshOnly: refreshOnly ?? false,
4599
+ ...params.journal === void 0 ? {} : { journal: params.journal }
4600
+ });
4601
+ const { skillIds, skillWriter } = agentsSkillSlot({
4602
+ projectDir,
4603
+ selectedInternalSkills,
4604
+ refreshOnly: refreshOnly ?? false,
4605
+ writer
4606
+ });
4607
+ const antigravityInstalled = params.installedByRecord === true;
4608
+ const harnessRuleFiles = [];
4609
+ for (const rule of portRules(harnessRoot, rules)) {
4610
+ const target = join15(projectDir, ".agents", "rules", `${rule.name}.md`);
4611
+ const content = `${antigravityFrontmatter(rule)}${rule.body}
4612
+ `;
4613
+ if (!writer.write(target, content, { createInRefresh: antigravityInstalled })) {
4614
+ continue;
4615
+ }
4616
+ harnessRuleFiles.push(target);
4617
+ }
4618
+ const skillFiles = writeBundledSkillDirs({
4619
+ harnessRoot,
4620
+ projectDir,
4621
+ skillIds,
4622
+ writer: skillWriter
4623
+ });
4624
+ return {
4625
+ harnessRuleFiles,
4626
+ skillFiles,
4627
+ ownership: writer.result()
4628
+ };
4629
+ }
4630
+
4631
+ // src/codex/opt-in.ts
4632
+ init_esm_shims();
4633
+ import { homedir } from "os";
4634
+ import { join as join16 } from "path";
4635
+
4636
+ // src/codex/trust-entry.ts
4637
+ init_esm_shims();
4638
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync11, realpathSync as realpathSync2, writeFileSync as writeFileSync6 } from "fs";
4639
+ import { dirname as dirname4 } from "path";
4640
+ var UNSAFE_APPEND = "adding the entry would make this file unreadable TOML \u2014 left untouched";
4641
+ function trustKeysFor(projectDir) {
4642
+ let canonical = projectDir;
4643
+ try {
4644
+ canonical = realpathSync2.native(projectDir);
4645
+ } catch {
4368
4646
  }
4369
- if (recorded2.size === 0) {
4370
- const names = params.remnant(disk);
4371
- return names.length === 0 ? out("none") : out("left", { line: params.remnantLine(names) });
4647
+ const keys = [canonical, projectDir];
4648
+ const out = process.platform === "win32" ? keys.map((k2) => k2.replace(/[A-Z]/g, (ch) => ch.toLowerCase())) : keys;
4649
+ return [...new Set(out)];
4650
+ }
4651
+ function tomlBasicString(s) {
4652
+ let out = '"';
4653
+ for (const ch of s) {
4654
+ const code = ch.codePointAt(0) ?? 0;
4655
+ if (ch === "\\") out += "\\\\";
4656
+ else if (ch === '"') out += '\\"';
4657
+ else if (code <= 31 || code === 127)
4658
+ out += `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
4659
+ else out += ch;
4372
4660
  }
4373
- const impl = ADAPTERS[adapter];
4374
- const res = impl.strip(disk, {
4375
- recorded: recorded2,
4376
- excluded: excludedKeys(path, params.excluded),
4377
- projectDir
4378
- });
4379
- if (!res.ok) {
4380
- return out("left", {
4381
- line: `could not read it (${res.reason}) \u2014 harness part not removed`,
4382
- portions: toPortions(path, recorded2)
4383
- });
4661
+ return `${out}"`;
4662
+ }
4663
+ function isTable2(v2) {
4664
+ return typeof v2 === "object" && v2 !== null && !Array.isArray(v2) && !(v2 instanceof Date);
4665
+ }
4666
+ function findEntry(parsed, keys) {
4667
+ const projects = parsed.projects;
4668
+ if (!isTable2(projects)) return null;
4669
+ for (const key of keys) {
4670
+ if (!Object.hasOwn(projects, key)) continue;
4671
+ const entry = projects[key];
4672
+ return { key, level: isTable2(entry) ? entry.trust_level : void 0 };
4673
+ }
4674
+ return null;
4675
+ }
4676
+ function registerTrustEntry(opts) {
4677
+ const { configPath, projectDir } = opts;
4678
+ const keys = trustKeysFor(projectDir);
4679
+ const key = keys[0] ?? projectDir;
4680
+ try {
4681
+ const existing = existsSync12(configPath) ? readFileSync11(configPath, "utf8") : "";
4682
+ let parsed;
4683
+ try {
4684
+ parsed = parse(existing);
4685
+ } catch (e2) {
4686
+ return {
4687
+ status: "unreadable",
4688
+ message: `not valid TOML (${firstLine(e2)}) \u2014 left untouched`
4689
+ };
4690
+ }
4691
+ const found = findEntry(parsed, keys);
4692
+ if (found !== null) {
4693
+ if (found.level === "trusted") return { status: "already-present", key: found.key };
4694
+ const level = typeof found.level === "string" ? `trust_level = ${tomlBasicString(found.level)}` : "no trust_level";
4695
+ return { status: "left", key: found.key, message: `already present (${level}) \u2014 left as is` };
4696
+ }
4697
+ const next = `${existing}
4698
+ [projects.${tomlBasicString(key)}]
4699
+ trust_level = "trusted"
4700
+ `;
4701
+ if (findEntry(readable(next) ?? {}, [key])?.level !== "trusted") {
4702
+ return { status: "error", key, message: UNSAFE_APPEND };
4703
+ }
4704
+ mkdirSync4(dirname4(configPath), { recursive: true });
4705
+ writeFileSync6(configPath, next);
4706
+ return { status: "registered", key };
4707
+ } catch (e2) {
4708
+ return {
4709
+ status: "error",
4710
+ key,
4711
+ message: e2 instanceof Error ? e2.message : String(e2)
4712
+ };
4713
+ }
4714
+ }
4715
+ function hasTrustEntry(configContent, projectDir) {
4716
+ const parsed = readable(configContent);
4717
+ return parsed !== null && findEntry(parsed, trustKeysFor(projectDir))?.level === "trusted";
4718
+ }
4719
+ function readable(text) {
4720
+ try {
4721
+ return parse(text);
4722
+ } catch {
4723
+ return null;
4384
4724
  }
4385
- const fileRemoved = params.removeIfEmpty === true && res.removed.length > 0 && res.empty;
4386
- if (params.write && fileRemoved) rmSync(abs);
4387
- else if (params.write && res.changed) writeFileSync6(abs, res.text);
4388
- const action = res.removed.length > 0 ? "removed" : res.kept.length > 0 ? "left" : "none";
4389
- return out(action, {
4390
- removed: res.removed,
4391
- kept: res.kept,
4392
- portions: toPortions(path, res.portions),
4393
- ...fileRemoved ? { fileRemoved } : {}
4394
- });
4395
4725
  }
4726
+ function firstLine(e2) {
4727
+ return (e2 instanceof Error ? e2.message : String(e2)).split("\n")[0] ?? "";
4728
+ }
4729
+
4730
+ // src/codex/opt-in.ts
4731
+ function runCodexOptIn(ctx) {
4732
+ const codexHome = ctx.codexHome ?? join16(homedir(), ".codex");
4733
+ const configPath = join16(codexHome, "config.toml");
4734
+ const result = registerTrustEntry({ configPath, projectDir: ctx.projectDir });
4735
+ return {
4736
+ trustEntry: {
4737
+ enabled: true,
4738
+ status: result.status,
4739
+ configPath,
4740
+ ...result.message ? { message: result.message } : {},
4741
+ ...result.key ? { key: result.key } : {}
4742
+ }
4743
+ };
4744
+ }
4745
+
4746
+ // src/codex/transform.ts
4747
+ init_esm_shims();
4748
+ import { chmodSync as chmodSync2, existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
4749
+ import { basename as basename2, join as join18 } from "path";
4396
4750
 
4397
4751
  // src/codex/config-toml.ts
4398
4752
  init_esm_shims();
4753
+ import { readFileSync as readFileSync12 } from "fs";
4754
+ import { join as join17 } from "path";
4399
4755
  var TABLES_REGION = "tables";
4400
4756
  function configRegions(rendered) {
4401
4757
  const lines2 = rendered.split(/\r?\n/);
@@ -4431,6 +4787,20 @@ function hasLegacyHarnessHook(disk) {
4431
4787
  const { session_start: _flat, ...rest } = hooks;
4432
4788
  return flat && !callsHarnessHook({ ...parsed, hooks: rest });
4433
4789
  }
4790
+ var RETIRED_FEATURE_KEYS = ["codex_hooks", "child_agents_md"];
4791
+ function retiredFeatureKeys(disk) {
4792
+ const features = readToml(disk)?.features;
4793
+ if (typeof features !== "object" || features === null || Array.isArray(features)) return [];
4794
+ return RETIRED_FEATURE_KEYS.filter((k2) => Object.hasOwn(features, k2));
4795
+ }
4796
+ function retiredFeaturesLeftBehind(projectDir, r) {
4797
+ if (r.action !== "left" && !r.leftAsIs.includes(TABLES_REGION)) return [];
4798
+ try {
4799
+ return retiredFeatureKeys(readFileSync12(join17(projectDir, ".codex/config.toml"), "utf8"));
4800
+ } catch {
4801
+ return [];
4802
+ }
4803
+ }
4434
4804
  function flatHookCommands(value) {
4435
4805
  if (!Array.isArray(value)) return [];
4436
4806
  return value.flatMap((entry) => {
@@ -4444,7 +4814,7 @@ function isChangedLegacyConfig(disk, baselineSha) {
4444
4814
  return parsed !== null && callsHarnessHook(parsed);
4445
4815
  }
4446
4816
  function renderConfigToml(params) {
4447
- const substituted = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_DIR}", params.projectDir).replaceAll("{GITHUB_TOKEN}", "${GITHUB_TOKEN}");
4817
+ const substituted = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_DIR}", tomlBasicString(params.projectDir).slice(1, -1)).replaceAll("{GITHUB_TOKEN}", "${GITHUB_TOKEN}");
4448
4818
  if (!params.mcp) {
4449
4819
  return substituted;
4450
4820
  }
@@ -4481,20 +4851,16 @@ function runCodexTransform(params) {
4481
4851
  harnessRoot,
4482
4852
  projectDir,
4483
4853
  selectedInternalSkills = [],
4484
- rules = [],
4485
4854
  mcp,
4486
4855
  baseline,
4487
4856
  refreshOnly,
4488
- writeAgentsMd: writesAgentsMd = true,
4489
4857
  shared: shared2 = {}
4490
4858
  } = params;
4491
4859
  const writer = createOwnedWriter(projectDir, baseline, {
4492
4860
  refreshOnly: refreshOnly ?? false,
4493
4861
  ...params.journal === void 0 ? {} : { journal: params.journal }
4494
4862
  });
4495
- const claudeMd = readRequired(join15(harnessRoot, "templates/CLAUDE.md"));
4496
- const agentsTemplate = readRequired(join15(harnessRoot, "templates/codex/AGENTS.md.template"));
4497
- const configTemplate = readRequired(join15(harnessRoot, "templates/codex/config.toml.template"));
4863
+ const configTemplate = readRequired2(join18(harnessRoot, "templates/codex/config.toml.template"));
4498
4864
  const projectName = basename2(projectDir);
4499
4865
  const { skillIds, skillWriter } = agentsSkillSlot({
4500
4866
  projectDir,
@@ -4502,37 +4868,7 @@ function runCodexTransform(params) {
4502
4868
  refreshOnly: refreshOnly ?? false,
4503
4869
  writer
4504
4870
  });
4505
- const agentsMdPath = join15(projectDir, "AGENTS.md");
4506
- const seededContext = !writesAgentsMd || refreshOnly || existsSync13(agentsMdPath) ? null : seedAgentsMdProjectContext(projectDir);
4507
- ensureDir(projectDir);
4508
- let agentsMd = null;
4509
- if (writesAgentsMd) {
4510
- const agentsMdOut = renderAgentsMd({
4511
- template: agentsTemplate,
4512
- claudeMd,
4513
- projectName,
4514
- // ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
4515
- // #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다.
4516
- // #530 — `skillIds` 를 쓴다(= 깔린 것 + **이번 실행이 만들 것**).
4517
- projectContext: withMarkedContinuousSkillsNote(
4518
- seededContext ?? renderFillScaffold("agents-md"),
4519
- skillIds
4520
- ),
4521
- // Codex 는 룰 디렉터리가 없다 — 룰이 AGENTS.md 본문에 들어가야 도달한다(§Harness Rules).
4522
- harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
4523
- });
4524
- agentsMd = writeAgentsMd({
4525
- projectDir,
4526
- rendered: agentsMdOut,
4527
- template: agentsTemplate,
4528
- anchorTitle: anchorTitle(claudeMd),
4529
- writer,
4530
- baseline,
4531
- record: shared2,
4532
- refreshOnly: refreshOnly ?? false
4533
- });
4534
- }
4535
- const configTomlPath = join15(projectDir, CONFIG_TOML);
4871
+ const configTomlPath = join18(projectDir, CONFIG_TOML);
4536
4872
  const configToml = writeConfigToml({
4537
4873
  projectDir,
4538
4874
  regions: configRegions(
@@ -4543,15 +4879,15 @@ function runCodexTransform(params) {
4543
4879
  record: shared2,
4544
4880
  refreshOnly: refreshOnly ?? false
4545
4881
  });
4546
- const hookDir = join15(projectDir, ".codex/hooks");
4882
+ const hookDir = join18(projectDir, ".codex/hooks");
4547
4883
  const hookFiles = [];
4548
4884
  for (const hook of HOOK_NAMES) {
4549
- const src = join15(harnessRoot, "templates/hooks", `${hook}.sh`);
4885
+ const src = join18(harnessRoot, "templates/hooks", `${hook}.sh`);
4550
4886
  if (!existsSync13(src)) {
4551
4887
  continue;
4552
4888
  }
4553
- const ported = readFileSync11(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
4554
- const target = join15(hookDir, `${hook}.sh`);
4889
+ const ported = readFileSync13(src, "utf8").replace(ENV_VAR_RENAME, "CODEX_PROJECT_DIR");
4890
+ const target = join18(hookDir, `${hook}.sh`);
4555
4891
  if (!writer.write(target, ported)) continue;
4556
4892
  chmodSync2(target, 493);
4557
4893
  hookFiles.push(target);
@@ -4563,11 +4899,8 @@ function runCodexTransform(params) {
4563
4899
  writer: skillWriter
4564
4900
  });
4565
4901
  return {
4566
- agentsMdPath,
4567
- agentsMdSeededFrom: seededContext === null ? null : "CLAUDE.md",
4568
4902
  configTomlPath,
4569
4903
  configToml,
4570
- agentsMd,
4571
4904
  hookFiles,
4572
4905
  skillFiles,
4573
4906
  ownership: writer.result()
@@ -4576,8 +4909,8 @@ function runCodexTransform(params) {
4576
4909
  var CONFIG_TOML = ".codex/config.toml";
4577
4910
  var LEGACY_CHANGED = "written by an earlier harness version and changed since \u2014 left as is; harness part not added";
4578
4911
  function writeConfigToml(args) {
4579
- const abs = join15(args.projectDir, CONFIG_TOML);
4580
- const disk = existsSync13(abs) ? readFileSync11(abs, "utf8") : null;
4912
+ const abs = join18(args.projectDir, CONFIG_TOML);
4913
+ const disk = existsSync13(abs) ? readFileSync13(abs, "utf8") : null;
4581
4914
  const regions = disk === null ? null : ADAPTERS["toml-region"].read(disk);
4582
4915
  if (disk !== null && regions?.size === 0 && isChangedLegacyConfig(disk, args.baseline.get(CONFIG_TOML))) {
4583
4916
  return {
@@ -4606,16 +4939,16 @@ function writeConfigToml(args) {
4606
4939
  refreshOnly: args.refreshOnly
4607
4940
  });
4608
4941
  }
4609
- function readRequired(path) {
4942
+ function readRequired2(path) {
4610
4943
  if (!existsSync13(path)) {
4611
4944
  throw new Error(`Codex transform: required source missing: ${path}`);
4612
4945
  }
4613
- return readFileSync11(path, "utf8");
4946
+ return readFileSync13(path, "utf8");
4614
4947
  }
4615
4948
 
4616
4949
  // src/mcp-merge.ts
4617
4950
  init_esm_shims();
4618
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
4951
+ import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
4619
4952
  function parseTrackMcpMap(raw) {
4620
4953
  const rows = [];
4621
4954
  for (const line of raw.split(/\r?\n/)) {
@@ -4669,8 +5002,8 @@ function mergeMcpServers(base, rows, tracks, chosen = [], dropped = []) {
4669
5002
  return out;
4670
5003
  }
4671
5004
  function composeMcpJson(opts) {
4672
- const base = JSON.parse(readFileSync12(opts.templateMcpPath, "utf8"));
4673
- const mapRaw = existsSync14(opts.trackMapPath) ? readFileSync12(opts.trackMapPath, "utf8") : "";
5005
+ const base = JSON.parse(readFileSync14(opts.templateMcpPath, "utf8"));
5006
+ const mapRaw = existsSync14(opts.trackMapPath) ? readFileSync14(opts.trackMapPath, "utf8") : "";
4674
5007
  const rows = parseTrackMcpMap(mapRaw);
4675
5008
  return mergeMcpServers(base, rows, opts.tracks, opts.chosen(rows), opts.dropped?.(rows) ?? []);
4676
5009
  }
@@ -4712,19 +5045,8 @@ function recordedMcpAssetIds(log) {
4712
5045
 
4713
5046
  // src/opencode/transform.ts
4714
5047
  init_esm_shims();
4715
- import { existsSync as existsSync15, readdirSync, readFileSync as readFileSync13, unlinkSync } from "fs";
4716
- import { basename as basename3, join as join16 } from "path";
4717
-
4718
- // src/opencode/agents-md.ts
4719
- init_esm_shims();
4720
- function renameSlashes2(text) {
4721
- return text.replaceAll("/uzys:", "/uzys-");
4722
- }
4723
- function renderAgentsMd2(params) {
4724
- const body = wrapHarnessBlock(ANCHOR_BLOCK, params.claudeMd.replace(/^#\s+.*\r?\n/, "").trim());
4725
- const replaced = params.template.replaceAll("{PROJECT_NAME}", params.projectName).replaceAll("{PROJECT_RULES}", body).replaceAll("{PROJECT_CONTEXT}", params.projectContext).replaceAll("{HARNESS_RULES}", params.harnessRules ?? "");
4726
- return renameSlashes2(replaced);
4727
- }
5048
+ import { existsSync as existsSync15, readdirSync as readdirSync2, readFileSync as readFileSync15, unlinkSync } from "fs";
5049
+ import { join as join19 } from "path";
4728
5050
 
4729
5051
  // src/opencode/opencode-json.ts
4730
5052
  init_esm_shims();
@@ -4763,7 +5085,6 @@ function runOpencodeTransform(params) {
4763
5085
  harnessRoot,
4764
5086
  projectDir,
4765
5087
  selectedInternalSkills = [],
4766
- rules = [],
4767
5088
  mcp,
4768
5089
  baseline,
4769
5090
  refreshOnly,
@@ -4773,12 +5094,9 @@ function runOpencodeTransform(params) {
4773
5094
  refreshOnly: refreshOnly ?? false,
4774
5095
  ...params.journal === void 0 ? {} : { journal: params.journal }
4775
5096
  });
4776
- const claudeMd = readRequired2(join16(harnessRoot, "templates/CLAUDE.md"));
4777
- const agentsTemplate = readRequired2(join16(harnessRoot, "templates/opencode/AGENTS.md.template"));
4778
- const opencodeTemplate = readRequired2(
4779
- join16(harnessRoot, "templates/opencode/opencode.json.template")
5097
+ const opencodeTemplate = readRequired3(
5098
+ join19(harnessRoot, "templates/opencode/opencode.json.template")
4780
5099
  );
4781
- const projectName = basename3(projectDir);
4782
5100
  const { skillIds, skillWriter } = agentsSkillSlot({
4783
5101
  projectDir,
4784
5102
  selectedInternalSkills,
@@ -4786,36 +5104,7 @@ function runOpencodeTransform(params) {
4786
5104
  writer
4787
5105
  });
4788
5106
  ensureDir(projectDir);
4789
- const agentsMdPath = join16(projectDir, "AGENTS.md");
4790
- const seededContext = refreshOnly || existsSync15(agentsMdPath) ? null : seedAgentsMdProjectContext(projectDir);
4791
- const agentsMdOut = renderAgentsMd2({
4792
- template: agentsTemplate,
4793
- claudeMd,
4794
- projectName,
4795
- // ADR-085 — 상시 스킬 안내는 앵커가 아니라 여기(프로젝트 맥락)에, 깔린 것만.
4796
- // #503 — 그 조각은 설치자 소유 절 안에 사니 마커로 감싼다 (codex 와 같은 마커).
4797
- // #531 — `skillIds` 를 쓴다(= 깔린 것 + **이번 실행이 만들 것**). 안 그러면 이번에 새로
4798
- // 깐 상시 스킬이 다음 update 까지 안내에서 빠져 설치자 에이전트가 영영 안 연다.
4799
- projectContext: withMarkedContinuousSkillsNote(
4800
- seededContext ?? renderFillScaffold("agents-md"),
4801
- skillIds
4802
- ),
4803
- // codex 와 **같은 파일**(프로젝트 루트 `AGENTS.md`)이다. 두 transform 이 서로 다른 본문을
4804
- // 쓰면 나중에 도는 쪽이 앞선 쪽을 덮어써, codex+opencode 조합에서 룰이 통째로 사라진다
4805
- // (독립 검증 C-1 실측). 같은 내용을 쓰면 순서가 결과를 바꾸지 않는다.
4806
- harnessRules: renderRulesBlock(portRules(harnessRoot, rules))
4807
- });
4808
- const agentsMd = writeAgentsMd({
4809
- projectDir,
4810
- rendered: agentsMdOut,
4811
- template: agentsTemplate,
4812
- anchorTitle: anchorTitle(claudeMd),
4813
- writer,
4814
- baseline,
4815
- record: shared2,
4816
- refreshOnly: refreshOnly ?? false
4817
- });
4818
- const opencodeJsonPath = join16(projectDir, OPENCODE_JSON);
5107
+ const opencodeJsonPath = join19(projectDir, OPENCODE_JSON);
4819
5108
  const render = renderOpencodeMcp(mcp);
4820
5109
  const opencodeJson = writeShared({
4821
5110
  projectDir,
@@ -4834,13 +5123,13 @@ function runOpencodeTransform(params) {
4834
5123
  writer: skillWriter
4835
5124
  });
4836
5125
  const retiredCommands = [];
4837
- const cmdDir = join16(projectDir, ".opencode/commands");
5126
+ const cmdDir = join19(projectDir, ".opencode/commands");
4838
5127
  if (existsSync15(cmdDir)) {
4839
- for (const entry of readdirSync(cmdDir)) {
5128
+ for (const entry of readdirSync2(cmdDir)) {
4840
5129
  if (!entry.endsWith(".md")) continue;
4841
5130
  const id = entry.slice(0, -3);
4842
- if (!existsSync15(join16(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
4843
- const victim = join16(cmdDir, entry);
5131
+ if (!existsSync15(join19(harnessRoot, "templates/skills", id, "SKILL.md"))) continue;
5132
+ const victim = join19(cmdDir, entry);
4844
5133
  if (writer.skipOutside(victim)) continue;
4845
5134
  try {
4846
5135
  backupFile(victim);
@@ -4851,22 +5140,19 @@ function runOpencodeTransform(params) {
4851
5140
  }
4852
5141
  }
4853
5142
  return {
4854
- agentsMdPath,
4855
- agentsMdSeededFrom: seededContext === null ? null : "CLAUDE.md",
4856
5143
  opencodeJsonPath,
4857
5144
  opencodeJson,
4858
- agentsMd,
4859
5145
  skillFiles,
4860
5146
  retiredCommands,
4861
5147
  ownership: writer.result()
4862
5148
  };
4863
5149
  }
4864
5150
  var OPENCODE_JSON = "opencode.json";
4865
- function readRequired2(path) {
5151
+ function readRequired3(path) {
4866
5152
  if (!existsSync15(path)) {
4867
5153
  throw new Error(`OpenCode transform: required source missing: ${path}`);
4868
5154
  }
4869
- return readFileSync13(path, "utf8");
5155
+ return readFileSync15(path, "utf8");
4870
5156
  }
4871
5157
 
4872
5158
  // src/cli-transforms.ts
@@ -4888,7 +5174,7 @@ function trackDefaultMcpAssetIds(harnessRoot, tracks) {
4888
5174
  }
4889
5175
  function trackRows(harnessRoot) {
4890
5176
  const { trackMapPath } = mcpSources(harnessRoot);
4891
- return existsSync16(trackMapPath) ? parseTrackMcpMap(readFileSync14(trackMapPath, "utf8")) : [];
5177
+ return existsSync16(trackMapPath) ? parseTrackMcpMap(readFileSync16(trackMapPath, "utf8")) : [];
4892
5178
  }
4893
5179
  function renderEverySelectableMcp(harnessRoot, tracks) {
4894
5180
  return composeMcpJson({
@@ -4908,8 +5194,8 @@ function adapterExcluded(harnessRoot, excluded) {
4908
5194
  }
4909
5195
  function mcpSources(harnessRoot) {
4910
5196
  return {
4911
- templateMcpPath: join17(harnessRoot, "templates/mcp.json"),
4912
- trackMapPath: join17(harnessRoot, "templates/track-mcp-map.tsv")
5197
+ templateMcpPath: join20(harnessRoot, "templates/mcp.json"),
5198
+ trackMapPath: join20(harnessRoot, "templates/track-mcp-map.tsv")
4913
5199
  };
4914
5200
  }
4915
5201
  var ALL_CLI_TARGETS = CLI_BASES;
@@ -4929,6 +5215,8 @@ function runCliTransforms(params) {
4929
5215
  journal
4930
5216
  } = params;
4931
5217
  const journalParam = journal === void 0 ? {} : { journal };
5218
+ const legacyContext = readLegacyAntigravityContext(projectDir, previousExternal);
5219
+ const outside = createOutsideGuard(projectDir);
4932
5220
  const baseline = new Map(previousExternal.map((f) => [f.path, f.sha256]));
4933
5221
  const externalFiles = [];
4934
5222
  const externalBackups = [];
@@ -4968,19 +5256,14 @@ function runCliTransforms(params) {
4968
5256
  harnessRoot,
4969
5257
  projectDir,
4970
5258
  selectedInternalSkills,
4971
- rules,
4972
5259
  mcp,
4973
5260
  baseline,
4974
5261
  refreshOnly,
4975
5262
  shared: shared2,
4976
- // #550 — 같은 `AGENTS.md` 를 한 실행에서 쓰는 쪽은 하나다. opencode 가 뒤에서 자기 템플릿으로
4977
- // 쓰면 codex 판은 어차피 같은 실행 안에서 덮였고(최종 파일 = opencode 판), 매 실행 codex 가
4978
- // opencode 판을 자기 판으로 뒤집는 한 번의 쓰기가 설치자 편집분을 백업으로 쌓았다.
4979
- writeAgentsMd: !cli.includes("opencode"),
4980
5263
  ...journalParam
4981
5264
  });
4982
5265
  absorb(codex);
4983
- absorbShared(codex, [codex.agentsMd?.shared, codex.configToml]);
5266
+ absorbShared(codex, [codex.configToml]);
4984
5267
  if (codexTrust) {
4985
5268
  codexOptIn = runCodexOptIn({ projectDir });
4986
5269
  journal?.trust(codexOptIn);
@@ -4993,7 +5276,6 @@ function runCliTransforms(params) {
4993
5276
  harnessRoot,
4994
5277
  projectDir,
4995
5278
  selectedInternalSkills,
4996
- rules,
4997
5279
  mcp,
4998
5280
  baseline,
4999
5281
  refreshOnly,
@@ -5001,7 +5283,7 @@ function runCliTransforms(params) {
5001
5283
  ...journalParam
5002
5284
  });
5003
5285
  absorb(opencode);
5004
- absorbShared(opencode, [opencode.agentsMd?.shared, opencode.opencodeJson]);
5286
+ absorbShared(opencode, [opencode.opencodeJson]);
5005
5287
  journal?.done("opencode");
5006
5288
  }
5007
5289
  let antigravity = null;
@@ -5019,11 +5301,47 @@ function runCliTransforms(params) {
5019
5301
  absorb(antigravity);
5020
5302
  journal?.done("antigravity");
5021
5303
  }
5304
+ const agentsSet = agentsMdClis([...cli, ...recordedClis2]);
5305
+ let agentsMd = null;
5306
+ if (agentsSet.length > 0) {
5307
+ const beyondThisRun = agentsSet.some((c3) => !cli.includes(c3));
5308
+ agentsMd = runAgentsMdTransform({
5309
+ harnessRoot,
5310
+ projectDir,
5311
+ clis: agentsSet,
5312
+ selectedInternalSkills,
5313
+ rules: beyondThisRun && params.cumulativeRules !== void 0 ? params.cumulativeRules : rules,
5314
+ baseline,
5315
+ refreshOnly,
5316
+ // ADR-099 R2 — 기록이 이 파일을 읽는 CLI 를 깔린 것으로 말하면 없어도 만든다(26.164.0 이하 Antigravity 단독 설치본은
5317
+ // 첫 update 에 이 파일이 생긴다)
5318
+ installedByRecord: agentsSet.some((c3) => recordedClis2.includes(c3)),
5319
+ shared: shared2,
5320
+ legacyContext,
5321
+ ...journalParam
5322
+ });
5323
+ absorb(agentsMd);
5324
+ absorbShared(agentsMd, [agentsMd.agentsMd.shared]);
5325
+ }
5326
+ const anchorRetired = retireAntigravityAnchor({
5327
+ projectDir,
5328
+ previous: previousExternal,
5329
+ outside,
5330
+ hadContent: legacyContext !== null,
5331
+ moved: agentsMd?.agentsMdSeededFrom === LEGACY_ANTIGRAVITY_ANCHOR
5332
+ });
5333
+ if (anchorRetired?.backup) {
5334
+ externalBackups.push(anchorRetired.backup);
5335
+ externalBackedUp.push(anchorRetired.path);
5336
+ }
5337
+ externalOutside = mergeOutside(externalOutside, outside.list());
5022
5338
  return {
5023
5339
  codex,
5024
5340
  codexOptIn,
5025
5341
  opencode,
5026
5342
  antigravity,
5343
+ agentsMd,
5344
+ anchorRetired,
5027
5345
  externalFiles,
5028
5346
  externalBackups,
5029
5347
  externalUpdated,
@@ -5040,7 +5358,7 @@ function runCliTransforms(params) {
5040
5358
  // src/excluded-still-there.ts
5041
5359
  init_esm_shims();
5042
5360
  import { existsSync as existsSync17 } from "fs";
5043
- import { join as join18 } from "path";
5361
+ import { join as join21 } from "path";
5044
5362
  function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
5045
5363
  const out = [];
5046
5364
  const add = (id, catalog) => {
@@ -5053,7 +5371,7 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
5053
5371
  for (const id of excluded) {
5054
5372
  if (INTERNAL_BUNDLED_SKILL_IDS.includes(id)) {
5055
5373
  const there = [".claude/skills", ".agents/skills"].some(
5056
- (d2) => existsSync17(join18(projectDir, d2, id))
5374
+ (d2) => existsSync17(join21(projectDir, d2, id))
5057
5375
  );
5058
5376
  if (there) add(id, false);
5059
5377
  } else {
@@ -5066,17 +5384,17 @@ function excludedStillThere(projectDir, excluded, baselineOnDisk, previousLog) {
5066
5384
  function assetStillThere(projectDir, asset) {
5067
5385
  if (asset.method !== "skill" || asset.scope === "global") return true;
5068
5386
  if (asset.files !== void 0)
5069
- return asset.files.some((f) => existsSync17(join18(projectDir, f.path)));
5387
+ return asset.files.some((f) => existsSync17(join21(projectDir, f.path)));
5070
5388
  const dir = asset.detail.skill ?? asset.id;
5071
- return [".claude/skills", ".agents/skills"].some((d2) => existsSync17(join18(projectDir, d2, dir)));
5389
+ return [".claude/skills", ".agents/skills"].some((d2) => existsSync17(join21(projectDir, d2, dir)));
5072
5390
  }
5073
5391
 
5074
5392
  // src/external-installer.ts
5075
5393
  init_esm_shims();
5076
5394
  import { spawnSync } from "child_process";
5077
- import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
5395
+ import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
5078
5396
  import { homedir as homedir2 } from "os";
5079
- import { join as join19 } from "path";
5397
+ import { join as join22 } from "path";
5080
5398
  var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
5081
5399
  function skillRootOf(cli) {
5082
5400
  return cli === "claude" ? ".claude/skills" : ".agents/skills";
@@ -5084,11 +5402,11 @@ function skillRootOf(cli) {
5084
5402
  function snapshotSkillRoots(projectDir) {
5085
5403
  const out = /* @__PURE__ */ new Map();
5086
5404
  for (const root of SKILL_ROOTS) {
5087
- if (outsideProjectTarget(projectDir, join19(projectDir, root)) !== null) continue;
5088
- for (const rel of listFilesRecursive(join19(projectDir, root))) {
5405
+ if (outsideProjectTarget(projectDir, join22(projectDir, root)) !== null) continue;
5406
+ for (const rel of listFilesRecursive(join22(projectDir, root))) {
5089
5407
  const path = `${root}/${rel}`;
5090
5408
  try {
5091
- out.set(path, hashContent(readFileSync15(join19(projectDir, path), "utf8")));
5409
+ out.set(path, hashContent(readFileSync17(join22(projectDir, path), "utf8")));
5092
5410
  } catch {
5093
5411
  }
5094
5412
  }
@@ -5165,7 +5483,7 @@ function installOne(asset, ctx) {
5165
5483
  return runSpawn(asset, ctx.spawn, "npx", buildSkillArgs(method, ctx.cli, ctx.scope), cwd);
5166
5484
  }
5167
5485
  const outside = SKILL_ROOTS.flatMap((root) => {
5168
- const hit = outsideProjectTarget(cwd, join19(cwd, root));
5486
+ const hit = outsideProjectTarget(cwd, join22(cwd, root));
5169
5487
  return hit === null ? [] : [{ root, target: hit.target }];
5170
5488
  });
5171
5489
  const outRoots = new Set(outside.map((o) => o.root));
@@ -5346,9 +5664,9 @@ function detectVersion(method, spawn, scope, projectDir) {
5346
5664
  case "plugin":
5347
5665
  return detectPluginVersion(method.pluginId, scope, projectDir);
5348
5666
  case "npm": {
5349
- const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join19(projectDir, "node_modules", method.pkg, "package.json");
5667
+ const pkgJson = scope === "global" ? detectGlobalNpmPackageJsonPath(method.pkg, spawn) : join22(projectDir, "node_modules", method.pkg, "package.json");
5350
5668
  if (!pkgJson || !existsSync18(pkgJson)) return void 0;
5351
- const parsed = JSON.parse(readFileSync15(pkgJson, "utf8"));
5669
+ const parsed = JSON.parse(readFileSync17(pkgJson, "utf8"));
5352
5670
  return parsed.version;
5353
5671
  }
5354
5672
  default:
@@ -5360,9 +5678,9 @@ function detectVersion(method, spawn, scope, projectDir) {
5360
5678
  }
5361
5679
  function detectPluginVersion(pluginId, scope, projectDir) {
5362
5680
  const claudeScope = scope === "global" ? "user" : "project";
5363
- const path = join19(homedir2(), ".claude/plugins/installed_plugins.json");
5681
+ const path = join22(homedir2(), ".claude/plugins/installed_plugins.json");
5364
5682
  if (!existsSync18(path)) return void 0;
5365
- const data = JSON.parse(readFileSync15(path, "utf8"));
5683
+ const data = JSON.parse(readFileSync17(path, "utf8"));
5366
5684
  const entries = data.plugins?.[pluginId] ?? [];
5367
5685
  const matches = entries.filter(
5368
5686
  (e2) => claudeScope === "user" ? e2.scope === "user" : e2.scope === "project" && e2.projectPath === projectDir
@@ -5375,7 +5693,7 @@ function detectPluginVersion(pluginId, scope, projectDir) {
5375
5693
  }
5376
5694
  function detectGlobalNpmPackageJsonPath(pkg, spawn) {
5377
5695
  const npmRoot = getNpmGlobalRoot(spawn);
5378
- return npmRoot ? join19(npmRoot, pkg, "package.json") : void 0;
5696
+ return npmRoot ? join22(npmRoot, pkg, "package.json") : void 0;
5379
5697
  }
5380
5698
  var npmGlobalRootCache;
5381
5699
  function getNpmGlobalRoot(spawn) {
@@ -5394,8 +5712,8 @@ function getNpmGlobalRoot(spawn) {
5394
5712
 
5395
5713
  // src/key-ids.ts
5396
5714
  init_esm_shims();
5397
- import { readFileSync as readFileSync16 } from "fs";
5398
- import { join as join20 } from "path";
5715
+ import { readFileSync as readFileSync18 } from "fs";
5716
+ import { join as join23 } from "path";
5399
5717
  function renderedKeyIds(harnessRoot, input, clis) {
5400
5718
  const out = /* @__PURE__ */ new Set();
5401
5719
  const add = (path, key) => {
@@ -5418,7 +5736,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
5418
5736
  for (const name of Object.keys(mcp.mcpServers)) add(".mcp.json", `mcpServers.${name}`);
5419
5737
  for (const key of gitignoreRender().keys()) add(".gitignore", key);
5420
5738
  if (clis.includes("claude")) {
5421
- const template = readFileSync16(join20(harnessRoot, "templates/settings.json"), "utf8");
5739
+ const template = readFileSync18(join23(harnessRoot, "templates/settings.json"), "utf8");
5422
5740
  for (const key of renderSettingsPortion(template, () => true).keys()) {
5423
5741
  add(".claude/settings.json", key);
5424
5742
  }
@@ -5430,7 +5748,7 @@ function renderedKeyIds(harnessRoot, input, clis) {
5430
5748
  if (clis.includes("opencode")) {
5431
5749
  for (const key of renderOpencodeMcp(mcp).keys()) add("opencode.json", key);
5432
5750
  }
5433
- if (clis.includes("codex") || clis.includes("opencode")) add("AGENTS.md", AGENTS_BLOCK_NAME);
5751
+ if (clis.some(isAgentsMdCli)) add("AGENTS.md", AGENTS_BLOCK_NAME);
5434
5752
  return out;
5435
5753
  }
5436
5754
  function withoutAccepts(harnessRoot, spec, previous) {
@@ -5459,7 +5777,7 @@ function withoutAccepts(harnessRoot, spec, previous) {
5459
5777
 
5460
5778
  // src/linked-skill-bodies.ts
5461
5779
  init_esm_shims();
5462
- import { join as join21 } from "path";
5780
+ import { join as join24 } from "path";
5463
5781
  function refreshLinkedSkillBodies(params) {
5464
5782
  const { harnessRoot, projectDir, ids, baseline, journal } = params;
5465
5783
  const bodyKey = (id) => `.agents/skills/${id}/SKILL.md`;
@@ -5470,7 +5788,7 @@ function refreshLinkedSkillBodies(params) {
5470
5788
  );
5471
5789
  const result = writer.result();
5472
5790
  return {
5473
- updated: ours.filter((id) => written.has(join21(projectDir, bodyKey(id)))),
5791
+ updated: ours.filter((id) => written.has(join24(projectDir, bodyKey(id)))),
5474
5792
  notOurs: ids.filter((id) => !baseline.has(bodyKey(id))),
5475
5793
  files: result.files,
5476
5794
  backupPaths: result.backupPaths,
@@ -5481,8 +5799,8 @@ function refreshLinkedSkillBodies(params) {
5481
5799
 
5482
5800
  // src/out-of-track.ts
5483
5801
  init_esm_shims();
5484
- import { lstatSync as lstatSync2, readFileSync as readFileSync17, rmSync as rmSync2 } from "fs";
5485
- import { join as join22 } from "path";
5802
+ import { lstatSync as lstatSync3, readFileSync as readFileSync19, rmSync as rmSync3 } from "fs";
5803
+ import { join as join25 } from "path";
5486
5804
  var AGENTS_RULE = /^\.agents\/rules\/([^/]+)\.md$/;
5487
5805
  var ANCHOR_RULE = "uzys-harness";
5488
5806
  function reclaimOutOfTrack(args) {
@@ -5502,16 +5820,16 @@ function reclaimOutOfTrack(args) {
5502
5820
  const name = AGENTS_RULE.exec(f.path)?.[1];
5503
5821
  if (name === void 0 || name === ANCHOR_RULE || rendered.has(name)) continue;
5504
5822
  if (isBaselineExcluded(`.claude/rules/${name}.md`, args.excluded)) continue;
5505
- const abs = join22(projectDir, f.path);
5823
+ const abs = join25(projectDir, f.path);
5506
5824
  if (args.outside.skip(abs)) continue;
5507
- const stat = lstatSync2(abs, { throwIfNoEntry: false });
5825
+ const stat = lstatSync3(abs, { throwIfNoEntry: false });
5508
5826
  if (stat !== void 0 && !stat.isFile()) continue;
5509
5827
  if (stat !== void 0 && claudeCopy.has(`rules/${name}.md`)) {
5510
5828
  out.kept.push(f.path);
5511
5829
  out.bringBack[f.path] = tracksBringing(name);
5512
5830
  continue;
5513
5831
  }
5514
- const disk = stat === void 0 ? null : readFileSync17(abs, "utf8");
5832
+ const disk = stat === void 0 ? null : readFileSync19(abs, "utf8");
5515
5833
  const verdict = judge({
5516
5834
  op: "remove",
5517
5835
  kind: "harness",
@@ -5524,11 +5842,11 @@ function reclaimOutOfTrack(args) {
5524
5842
  out.bringBack[f.path] = tracksBringing(name);
5525
5843
  }
5526
5844
  if (verdict.verdict === "remove") {
5527
- rmSync2(abs);
5845
+ rmSync3(abs);
5528
5846
  out.removed.push(f.path);
5529
5847
  } else if (verdict.verdict === "backup+remove") {
5530
5848
  const backup = backupFile(abs, args.now);
5531
- rmSync2(abs);
5849
+ rmSync3(abs);
5532
5850
  out.backedUp.push({ path: f.path, backup });
5533
5851
  }
5534
5852
  }
@@ -5543,15 +5861,15 @@ function tracksBringing(rule) {
5543
5861
 
5544
5862
  // src/state.ts
5545
5863
  init_esm_shims();
5546
- import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
5547
- import { join as join23 } from "path";
5864
+ import { existsSync as existsSync19, readFileSync as readFileSync20 } from "fs";
5865
+ import { join as join26 } from "path";
5548
5866
  var META_FILE = ".claude/.installed-tracks";
5549
5867
  var LEGACY_ANCHOR_FILE = ".claude/CLAUDE.md";
5550
5868
  var TRACE_RULES = ["htmx", "nextjs", "data-analysis", "pyside6", "cli-development"].map(
5551
5869
  (r) => `.claude/rules/${r}.md`
5552
5870
  );
5553
5871
  function detectInstallState(projectDir) {
5554
- const hasClaudeDir = existsSync19(join23(projectDir, ".claude"));
5872
+ const hasClaudeDir = existsSync19(join26(projectDir, ".claude"));
5555
5873
  const read = readInstallLogStatus(projectDir);
5556
5874
  if (read.status === "ok") {
5557
5875
  return {
@@ -5571,8 +5889,8 @@ function tracksFromLog(tracks) {
5571
5889
  return [...new Set(tracks.filter(isTrack))].sort();
5572
5890
  }
5573
5891
  function collectTraces(projectDir) {
5574
- const at5 = (rel) => join23(projectDir, rel);
5575
- const contains = (rel, needle) => existsSync19(at5(rel)) && readFileSync18(at5(rel), "utf8").includes(needle);
5892
+ const at5 = (rel) => join26(projectDir, rel);
5893
+ const contains = (rel, needle) => existsSync19(at5(rel)) && readFileSync20(at5(rel), "utf8").includes(needle);
5576
5894
  const traces = [];
5577
5895
  if (existsSync19(at5(META_FILE)))
5578
5896
  traces.push({ path: META_FILE, tracks: readMetafile(at5(META_FILE)) });
@@ -5587,7 +5905,7 @@ function collectTraces(projectDir) {
5587
5905
  }
5588
5906
  function readMetafile(path) {
5589
5907
  const seen = /* @__PURE__ */ new Set();
5590
- for (const word of readFileSync18(path, "utf8").split(/\s+/)) {
5908
+ for (const word of readFileSync20(path, "utf8").split(/\s+/)) {
5591
5909
  if (isTrack(word)) seen.add(word);
5592
5910
  }
5593
5911
  return [...seen].sort();
@@ -5631,16 +5949,16 @@ init_esm_shims();
5631
5949
  import {
5632
5950
  copyFileSync,
5633
5951
  existsSync as existsSync20,
5634
- lstatSync as lstatSync3,
5952
+ lstatSync as lstatSync4,
5635
5953
  mkdirSync as mkdirSync5,
5636
- readdirSync as readdirSync2,
5637
- readFileSync as readFileSync19,
5954
+ readdirSync as readdirSync3,
5955
+ readFileSync as readFileSync21,
5638
5956
  rmdirSync,
5639
5957
  statSync as statSync3,
5640
5958
  unlinkSync as unlinkSync2,
5641
5959
  writeFileSync as writeFileSync7
5642
5960
  } from "fs";
5643
- import { basename as basename4, dirname as dirname5, join as join24 } from "path";
5961
+ import { basename as basename3, dirname as dirname5, join as join27 } from "path";
5644
5962
  var LEGACY_ANCHOR_FILE2 = ".claude/CLAUDE.md";
5645
5963
  function buildUpdateSpec(projectDir, tracks, only) {
5646
5964
  const log = readInstallLog(projectDir);
@@ -5662,7 +5980,7 @@ function parseUpdateOnly(raw) {
5662
5980
  return { ok: true, groups: [...new Set(raw)] };
5663
5981
  }
5664
5982
  function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
5665
- const claudeDir = join24(projectDir, ".claude");
5983
+ const claudeDir = join27(projectDir, ".claude");
5666
5984
  const startedAt = Date.now();
5667
5985
  const backupsAtStart = new Set(
5668
5986
  collectRunBackups(projectDir, Number.NEGATIVE_INFINITY).map((b2) => b2.backup)
@@ -5708,8 +6026,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
5708
6026
  const policyBase = policyBaseline(projectDir);
5709
6027
  for (const { dir, ext } of POLICY_DIRS) {
5710
6028
  if (!claudeManaged || !wants(dir === "hooks" ? "hooks" : "rules")) continue;
5711
- const target = join24(claudeDir, dir);
5712
- const source = join24(templatesDir, dir);
6029
+ const target = join27(claudeDir, dir);
6030
+ const source = join27(templatesDir, dir);
5713
6031
  const label = `.claude/${dir}`;
5714
6032
  const ctx = { prefix: dir, baseline: policyBase, outside, excluded: excludedIds(logAtStart) };
5715
6033
  const synced = updateDir(target, source, ext, ctx);
@@ -5723,8 +6041,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
5723
6041
  if (claudeManaged && syncedDirs.length > 0)
5724
6042
  refreshPolicyBaseline(projectDir, templatesDir, syncedDirs, outsidePaths(outside, ".claude/"));
5725
6043
  const skillSync = claudeManaged && wants("skills") ? syncSkills(
5726
- join24(claudeDir, "skills"),
5727
- join24(templatesDir, "skills"),
6044
+ join27(claudeDir, "skills"),
6045
+ join27(templatesDir, "skills"),
5728
6046
  skillBaseline(projectDir),
5729
6047
  /* @__PURE__ */ new Date(),
5730
6048
  (relInSkills) => foreignOwnedTarget(projectDir, `.claude/skills/${relInSkills}`),
@@ -5760,7 +6078,7 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
5760
6078
  droppedKeys: legacyDroppedKeys(logAtStart),
5761
6079
  harnessMcp
5762
6080
  });
5763
- const settingsPath = join24(claudeDir, "settings.json");
6081
+ const settingsPath = join27(claudeDir, "settings.json");
5764
6082
  if (claudeManaged && wants("hooks") && existsSync20(settingsPath) && !outside.skip(settingsPath)) {
5765
6083
  report.staleHookRefs = cleanStaleHookRefs(
5766
6084
  settingsPath,
@@ -5787,6 +6105,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
5787
6105
  restoredKeys: [],
5788
6106
  restoredFiles: [],
5789
6107
  legacyRestored: [],
6108
+ anchorRetired: null,
6109
+ agentsMd: null,
5790
6110
  excludedKeys: [],
5791
6111
  sharedLeft: []
5792
6112
  };
@@ -5796,6 +6116,8 @@ function runUpdateMode(projectDir, templatesDir, harnessRoot, deps = {}, only) {
5796
6116
  report.outOfTrack = outOfTrack;
5797
6117
  }
5798
6118
  report.externalUpdated = external.externalUpdated;
6119
+ if (external.anchorRetired !== null) report.anchorRetired = external.anchorRetired;
6120
+ if (external.agentsMd !== null) report.agentsMd = external.agentsMd;
5799
6121
  if (external.sharedLeft.length > 0) report.sharedLeft = external.sharedLeft;
5800
6122
  report.restoredKeys = external.restoredKeys;
5801
6123
  report.restoredFiles = external.restoredFiles;
@@ -5942,9 +6264,14 @@ function checksumOnRecord(log) {
5942
6264
  if (anchor !== void 0) paths.add(anchor.path);
5943
6265
  return (path) => paths.has(path);
5944
6266
  }
6267
+ function retiredFeaturesPart(projectDir, f) {
6268
+ if (f.path !== ".codex/config.toml") return {};
6269
+ const keys = retiredFeaturesLeftBehind(projectDir, f);
6270
+ return keys.length > 0 ? { retiredFeatures: keys } : {};
6271
+ }
5945
6272
  function legacyHookOnDisk(projectDir, rel) {
5946
6273
  try {
5947
- return hasLegacyHarnessHook(readFileSync19(join24(projectDir, rel), "utf8"));
6274
+ return hasLegacyHarnessHook(readFileSync21(join27(projectDir, rel), "utf8"));
5948
6275
  } catch {
5949
6276
  return false;
5950
6277
  }
@@ -5970,18 +6297,18 @@ function writeUpdateSharedFiles(args) {
5970
6297
  excluded: adapterExcluded(harnessRoot, excluded)
5971
6298
  });
5972
6299
  const tracks = installedTracks(projectDir);
5973
- const settingsSource = join24(templatesDir, "settings.json");
6300
+ const settingsSource = join27(templatesDir, "settings.json");
5974
6301
  if (args.claudeManaged && wants("hooks") && existsSync20(settingsSource)) {
5975
6302
  const spec = buildAssetSpec({ tracks, options: DEFAULT_OPTIONS });
5976
6303
  const manifest = buildManifest(spec);
5977
6304
  writeSettingsShared(
5978
6305
  writer,
5979
- readFileSync19(settingsSource, "utf8"),
6306
+ readFileSync21(settingsSource, "utf8"),
5980
6307
  projectDir,
5981
6308
  log,
5982
6309
  (script) => {
5983
6310
  const target = `.claude/hooks/${script}`;
5984
- return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) && existsSync20(join24(projectDir, target));
6311
+ return manifest.some((e2) => e2.target === target && e2.applies(spec)) && !isBaselineExcluded(target, excluded) && existsSync20(join27(projectDir, target));
5985
6312
  }
5986
6313
  // 기록이 claude 를 깔린 CLI 로 말한다 = install 이 이 파일을 썼다 — 없으면 만든다(훅 배선이 여기 산다)
5987
6314
  );
@@ -6019,30 +6346,30 @@ function writeUpdateSharedFiles(args) {
6019
6346
  }
6020
6347
  function staleSkillDirs(projectDir, claudeDir, clis) {
6021
6348
  const stale = (id) => RENAMED_SKILL_IDS[id] !== void 0 || RETIRED_SKILL_IDS.includes(id);
6022
- const dirs = [join24(claudeDir, "skills")];
6023
- if (clis.some((cli) => cli !== "claude")) dirs.push(join24(projectDir, ".agents", "skills"));
6349
+ const dirs = [join27(claudeDir, "skills")];
6350
+ if (clis.some((cli) => cli !== "claude")) dirs.push(join27(projectDir, ".agents", "skills"));
6024
6351
  const ids = /* @__PURE__ */ new Set();
6025
6352
  for (const dir of dirs) {
6026
6353
  if (!existsSync20(dir)) continue;
6027
- for (const e2 of readdirSync2(dir, { withFileTypes: true })) {
6354
+ for (const e2 of readdirSync3(dir, { withFileTypes: true })) {
6028
6355
  if (e2.isDirectory() && stale(e2.name)) ids.add(e2.name);
6029
6356
  }
6030
6357
  }
6031
6358
  return [...ids];
6032
6359
  }
6033
6360
  function staleAgentFiles(claudeDir) {
6034
- const dir = join24(claudeDir, "agents");
6361
+ const dir = join27(claudeDir, "agents");
6035
6362
  if (!existsSync20(dir)) return [];
6036
- return readdirSync2(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename4(e2.name, ".md")).filter((id) => RETIRED_AGENT_IDS.includes(id));
6363
+ return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename3(e2.name, ".md")).filter((id) => RETIRED_AGENT_IDS.includes(id));
6037
6364
  }
6038
6365
  function demotedAgentFiles(claudeDir, tracks) {
6039
6366
  if (tracks.length === 0) return [];
6040
- const dir = join24(claudeDir, "agents");
6367
+ const dir = join27(claudeDir, "agents");
6041
6368
  if (!existsSync20(dir)) return [];
6042
6369
  const demoted = TRACK_AGENTS.filter(([, pattern]) => !anyTrack(tracks, pattern)).map(
6043
6370
  ([id]) => id
6044
6371
  );
6045
- return readdirSync2(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename4(e2.name, ".md")).filter((id) => demoted.includes(id));
6372
+ return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => basename3(e2.name, ".md")).filter((id) => demoted.includes(id));
6046
6373
  }
6047
6374
  function installNewAssets(projectDir, templatesDir, wants, outside) {
6048
6375
  const installed = [];
@@ -6060,14 +6387,14 @@ function installNewAssets(projectDir, templatesDir, wants, outside) {
6060
6387
  if (!wants(group)) continue;
6061
6388
  if (entry.target === HARNESS_ANCHOR_FILE) continue;
6062
6389
  if (isBaselineExcluded(entry.target, baselineExcluded)) continue;
6063
- const target = join24(projectDir, entry.target);
6390
+ const target = join27(projectDir, entry.target);
6064
6391
  const foreign = foreignOwnedTarget(projectDir, entry.target);
6065
6392
  if (foreign !== null) {
6066
6393
  if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
6067
6394
  continue;
6068
6395
  }
6069
6396
  if (existsSync20(target)) continue;
6070
- const source = join24(templatesDir, entry.source);
6397
+ const source = join27(templatesDir, entry.source);
6071
6398
  if (!existsSync20(source)) continue;
6072
6399
  if (outside.skip(target)) continue;
6073
6400
  if (entry.target.startsWith(".claude/")) {
@@ -6094,20 +6421,20 @@ function collectRunBackups(projectDir, startedAt, exclude) {
6094
6421
  const consider = (rel) => {
6095
6422
  if (!BACKUP_SUFFIX.test(rel)) return;
6096
6423
  if (exclude?.has(rel)) return;
6097
- const abs = join24(projectDir, rel);
6424
+ const abs = join27(projectDir, rel);
6098
6425
  if (statSync3(abs).mtimeMs < startedAt - 1e3) return;
6099
6426
  out.push({ path: rel.replace(BACKUP_SUFFIX, ""), backup: rel });
6100
6427
  };
6101
- for (const e2 of readdirSync2(projectDir, { withFileTypes: true }))
6428
+ for (const e2 of readdirSync3(projectDir, { withFileTypes: true }))
6102
6429
  if (e2.isFile()) consider(e2.name);
6103
6430
  for (const dir of BACKUP_SCAN_DIRS) {
6104
- for (const rel of listFilesRecursive(join24(projectDir, dir))) consider(`${dir}/${rel}`);
6431
+ for (const rel of listFilesRecursive(join27(projectDir, dir))) consider(`${dir}/${rel}`);
6105
6432
  }
6106
6433
  return out.sort((a, b2) => a.path.localeCompare(b2.path));
6107
6434
  }
6108
6435
  function writeBackupList(projectDir, backups, noChecksum = /* @__PURE__ */ new Set()) {
6109
- const dir = join24(projectDir, ".uzys-agent-harness");
6110
- const file = join24(dir, "update-backups.json");
6436
+ const dir = join27(projectDir, ".uzys-agent-harness");
6437
+ const file = join27(dir, "update-backups.json");
6111
6438
  try {
6112
6439
  if (backups.length === 0) {
6113
6440
  if (existsSync20(file)) unlinkSync2(file);
@@ -6145,14 +6472,14 @@ function installNewSkillDirs(projectDir, templatesDir, tracks, outside) {
6145
6472
  if (entry.type !== "dir" || !entry.target.startsWith(".claude/skills/")) continue;
6146
6473
  if (!entry.applies(spec) || isBaselineExcluded(entry.target, excluded)) continue;
6147
6474
  if (skillExcluded.has(entry.target.slice(".claude/skills/".length))) continue;
6148
- const target = join24(projectDir, entry.target);
6475
+ const target = join27(projectDir, entry.target);
6149
6476
  const foreign = foreignOwnedTarget(projectDir, entry.target);
6150
6477
  if (foreign !== null) {
6151
6478
  if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
6152
6479
  continue;
6153
6480
  }
6154
6481
  if (existsSync20(target)) continue;
6155
- const source = join24(templatesDir, entry.source);
6482
+ const source = join27(templatesDir, entry.source);
6156
6483
  if (!existsSync20(source)) continue;
6157
6484
  if (outside.skip(target)) continue;
6158
6485
  copyDir(source, target, (rel) => foreignOwnedTarget(projectDir, `${entry.target}/${rel}`));
@@ -6183,17 +6510,17 @@ function trackOnlyFileAssets(tracks) {
6183
6510
  );
6184
6511
  }
6185
6512
  function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @__PURE__ */ new Date()) {
6186
- if (existsSync20(join24(projectDir, LEGACY_ANCHOR_FILE2))) {
6513
+ if (existsSync20(join27(projectDir, LEGACY_ANCHOR_FILE2))) {
6187
6514
  report.legacyAnchor = LEGACY_ANCHOR_FILE2;
6188
6515
  }
6189
- const templateMd = join24(templatesDir, "CLAUDE.md");
6516
+ const templateMd = join27(templatesDir, "CLAUDE.md");
6190
6517
  if (!existsSync20(templateMd)) return;
6191
- const anchor = join24(projectDir, HARNESS_ANCHOR_FILE);
6518
+ const anchor = join27(projectDir, HARNESS_ANCHOR_FILE);
6192
6519
  if (outside.skip(anchor)) return;
6193
6520
  const existed = existsSync20(anchor);
6194
6521
  if (existed) {
6195
- const current = readFileSync19(anchor, "utf8");
6196
- if (current !== readFileSync19(templateMd, "utf8")) {
6522
+ const current = readFileSync21(anchor, "utf8");
6523
+ if (current !== readFileSync21(templateMd, "utf8")) {
6197
6524
  const recorded2 = readInstallLog(projectDir)?.templates.rootClaudeMd;
6198
6525
  const owned = recorded2?.path === HARNESS_ANCHOR_FILE && hashContent(current) === recorded2.sha256;
6199
6526
  if (!owned) {
@@ -6214,16 +6541,16 @@ function syncHarnessAnchor(projectDir, templatesDir, report, outside, now = /* @
6214
6541
  recordAnchorBaseline(projectDir, anchor);
6215
6542
  }
6216
6543
  function upsertRootImport(projectDir, outside) {
6217
- const target = join24(projectDir, "CLAUDE.md");
6544
+ const target = join27(projectDir, "CLAUDE.md");
6218
6545
  if (outside.skip(target)) return false;
6219
- const existing = existsSync20(target) ? readFileSync19(target, "utf8") : null;
6546
+ const existing = existsSync20(target) ? readFileSync21(target, "utf8") : null;
6220
6547
  const next = upsertHarnessImport(existing, {
6221
- projectName: basename4(projectDir),
6548
+ projectName: basename3(projectDir),
6222
6549
  tracks: installedTracks(projectDir),
6223
6550
  // ADR-085 — update 는 선택 목록을 인자로 받지 않는다. `syncSkills` 와 같은 판정을 쓴다:
6224
6551
  // `.claude/skills/<id>` 가 있으면 그 스킬은 이 설치가 고른 것이다.
6225
6552
  continuousSkills: CONTINUOUS_SKILLS.map((s) => s.id).filter(
6226
- (id) => existsSync20(join24(projectDir, ".claude/skills", id))
6553
+ (id) => existsSync20(join27(projectDir, ".claude/skills", id))
6227
6554
  )
6228
6555
  });
6229
6556
  if (next === existing) return false;
@@ -6244,7 +6571,7 @@ function recordAnchorBaseline(projectDir, anchor) {
6244
6571
  ...log.templates,
6245
6572
  rootClaudeMd: {
6246
6573
  path: HARNESS_ANCHOR_FILE,
6247
- sha256: hashContent(readFileSync19(anchor, "utf8"))
6574
+ sha256: hashContent(readFileSync21(anchor, "utf8"))
6248
6575
  }
6249
6576
  }
6250
6577
  };
@@ -6258,16 +6585,34 @@ function excludedBaselineOnDisk(projectDir, log) {
6258
6585
  if (excluded.size === 0) return [];
6259
6586
  const spec = buildAssetSpec({ tracks: installedTracks(projectDir), options: DEFAULT_OPTIONS });
6260
6587
  return buildManifest(spec).filter(
6261
- (e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync20(join24(projectDir, e2.target))
6588
+ (e2) => e2.applies(spec) && isBaselineExcluded(e2.target, excluded) && existsSync20(join27(projectDir, e2.target))
6262
6589
  ).map((e2) => e2.target);
6263
6590
  }
6264
6591
  function excludedSkillOf(log) {
6265
6592
  const excluded = excludedIds(log);
6266
6593
  return (id) => excluded.has(id) || isBaselineExcluded(`.claude/skills/${id}`, excluded);
6267
6594
  }
6595
+ function recordedRenderInputs(projectDir, log) {
6596
+ const excluded = excludedIds(log);
6597
+ return {
6598
+ // 스킬은 **디스크에 있는 것만** 넘긴다. 스킬 *파일*은 전체 목록을 넘겨도 refreshOnly 가
6599
+ // 없는 것을 건너뛰지만, `AGENTS.md` 의 상시 스킬 안내(ADR-085)는 이 목록 그대로 렌더돼
6600
+ // `--without` 으로 뺀 스킬(#505)·opt-in 스킬을 "열어라"고 적었다(실측 2026-09-21). 판정은
6601
+ // `upsertRootImport` 와 같다 — `.claude/skills/<id>` 또는 `.agents/skills/<id>` 가 있으면 깔린 것.
6602
+ // ADR-099 R3 — 뺀 스킬은 디스크에 남아 있어도 갱신하지도 안내하지도 않는다(install 이 내는 것과 같은 목록)
6603
+ selectedInternalSkills: installedBundledSkills(projectDir).filter(
6604
+ (id) => !excludedSkillOf(log)(id)
6605
+ ),
6606
+ // #601 — install 과 같은 SSOT(resolveRules)를 설치된 트랙으로. 렌더 경로(AGENTS.md 절 · antigravity 룰 생성)는
6607
+ // 디스크 게이트가 없어, data 트랙 설치에서 update 만으로 cli-development(제6룰)가 생겼다.
6608
+ rules: resolveRules({ tracks: installedTracks(projectDir) }).filter(
6609
+ (r) => !isBaselineExcluded(`.claude/rules/${r}.md`, excluded)
6610
+ )
6611
+ };
6612
+ }
6268
6613
  function installedBundledSkills(projectDir) {
6269
6614
  return INTERNAL_BUNDLED_SKILL_IDS.filter(
6270
- (id) => existsSync20(join24(projectDir, ".claude/skills", id)) || existsSync20(join24(projectDir, ".agents/skills", id))
6615
+ (id) => existsSync20(join27(projectDir, ".claude/skills", id)) || existsSync20(join27(projectDir, ".agents/skills", id))
6271
6616
  );
6272
6617
  }
6273
6618
  function installedCliTargets(log) {
@@ -6297,9 +6642,9 @@ var SHARED_SKILLS_DIR = ".agents/skills";
6297
6642
  var SHARED_SKILL_FILE = /^\.agents\/skills\/([^/]+)\//;
6298
6643
  var SHARED_SKILL_MD = /^\.agents\/skills\/([^/]+)\/SKILL\.md$/;
6299
6644
  function sharedSkillIdsOnDisk(projectDir) {
6300
- const root = join24(projectDir, SHARED_SKILLS_DIR);
6645
+ const root = join27(projectDir, SHARED_SKILLS_DIR);
6301
6646
  if (!existsSync20(root)) return /* @__PURE__ */ new Set();
6302
- return new Set(readdirSync2(root).filter((id) => existsSync20(join24(root, id, "SKILL.md"))));
6647
+ return new Set(readdirSync3(root).filter((id) => existsSync20(join27(root, id, "SKILL.md"))));
6303
6648
  }
6304
6649
  var CREATED_SHARED_HOW = {
6305
6650
  "opencode.json": "created from template (harness part + template defaults)",
@@ -6307,40 +6652,33 @@ var CREATED_SHARED_HOW = {
6307
6652
  "AGENTS.md": "wrote the harness block only"
6308
6653
  };
6309
6654
  function cliArtifactOwners(path, installed) {
6310
- const owners = path.startsWith(".codex/") ? ["codex"] : path === "opencode.json" || path.startsWith(".opencode/") ? ["opencode"] : path === "AGENTS.md" ? ["codex", "opencode"] : path.startsWith(".agents/rules/") ? ["antigravity"] : [];
6655
+ const owners = path.startsWith(".codex/") ? ["codex"] : path === "opencode.json" || path.startsWith(".opencode/") ? ["opencode"] : path === "AGENTS.md" ? CLI_BASES.filter((c3) => CLI_OWNERSHIP[c3].some((o) => o.path === "AGENTS.md")) : path.startsWith(".agents/rules/") ? ["antigravity"] : [];
6311
6656
  return owners.filter((c3) => installed.includes(c3));
6312
6657
  }
6658
+ function agentsMdNews(report, existedBefore, priorPaths) {
6659
+ if (report === null) return null;
6660
+ const created = !existedBefore && !priorPaths.has("AGENTS.md") && report.ownership.files.some((f) => f.path === "AGENTS.md");
6661
+ if (!created && report.agentsMdSeededFrom === null) return null;
6662
+ return { created, clis: [...report.clis], seededFrom: report.agentsMdSeededFrom };
6663
+ }
6313
6664
  function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
6314
6665
  const log = readInstallLog(projectDir);
6315
- const baselineExcluded = excludedIds(log);
6316
6666
  const presentBefore = sharedSkillIdsOnDisk(projectDir);
6317
6667
  const rulesBefore = /* @__PURE__ */ new Set();
6318
- const rulesDir = join24(projectDir, ".agents", "rules");
6668
+ const rulesDir = join27(projectDir, ".agents", "rules");
6319
6669
  if (existsSync20(rulesDir)) {
6320
6670
  try {
6321
- for (const f of readdirSync2(rulesDir)) rulesBefore.add(f);
6671
+ for (const f of readdirSync3(rulesDir)) rulesBefore.add(f);
6322
6672
  } catch {
6323
6673
  }
6324
6674
  }
6675
+ const agentsMdBefore = existsSync20(join27(projectDir, "AGENTS.md"));
6325
6676
  const result = runCliTransforms({
6326
6677
  harnessRoot,
6327
6678
  projectDir,
6328
6679
  cli: installedCliTargets(log),
6329
- // 스킬은 **디스크에 있는 것만** 넘긴다. 스킬 *파일*은 전체 목록을 넘겨도 refreshOnly 가
6330
- // 없는 것을 건너뛰지만, `AGENTS.md` 의 상시 스킬 안내(ADR-085)는 이 목록 그대로 렌더돼
6331
- // `--without` 으로 뺀 스킬(#505)·opt-in 스킬을 "열어라"고 적었다(실측 2026-09-21). 판정은
6332
- // `upsertRootImport` 와 같다 — `.claude/skills/<id>` 또는 `.agents/skills/<id>` 가 있으면 깔린 것.
6333
- // ADR-099 R3 — 뺀 스킬은 디스크에 남아 있어도 갱신하지도 안내하지도 않는다(install 이 내는 것과 같은 목록)
6334
- selectedInternalSkills: installedBundledSkills(projectDir).filter(
6335
- (id) => !excludedSkillOf(log)(id)
6336
- ),
6337
- // #601 — install 과 같은 SSOT(resolveRules)를 설치된 트랙으로. ALL_RULES 문서는
6338
- // "refreshOnly 가 디스크로 대신 판정한다"고 했지만 렌더 경로(AGENTS.md 절 · antigravity 룰
6339
- // 생성)는 디스크 게이트가 없어, data 트랙 설치에서 update 만으로 cli-development(제6룰)가
6340
- // 생겼다. claude 레인은 처음부터 resolveRules 로 흐르므로 이쪽만 맞춘다.
6341
- rules: resolveRules({ tracks: installedTracks(projectDir) }).filter(
6342
- (r) => !isBaselineExcluded(`.claude/rules/${r}.md`, baselineExcluded)
6343
- ),
6680
+ // 스킬(디스크에 있는 것만) · 룰(기록 트랙) — `uninstall --cli` 의 `AGENTS.md` 재렌더와 같은 값(`recordedRenderInputs`)
6681
+ ...recordedRenderInputs(projectDir, log),
6344
6682
  // #568 — MCP 서버는 `.mcp.json` 과 같은 값(기록 트랙 · 기록된 선택 — 기록이 없으면 기본 서버만, 지어내지 않는다).
6345
6683
  mcp: harnessMcp,
6346
6684
  previousExternal: log?.externalFiles ?? [],
@@ -6353,8 +6691,11 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
6353
6691
  recordedClis: log === null ? [] : installedClis(log)
6354
6692
  });
6355
6693
  if (log) {
6356
- const merged = mergeExternalFiles(log.externalFiles, result.externalFiles);
6357
- const next = { ...log };
6694
+ const retiredPath = result.anchorRetired?.forget === true ? result.anchorRetired.path : null;
6695
+ const merged = mergeExternalFiles(log.externalFiles, result.externalFiles).filter(
6696
+ (f) => f.path !== retiredPath
6697
+ );
6698
+ const next = retiredPath === null ? { ...log } : { ...log, spec: { ...log.spec, clis: [...installedClis(log)] } };
6358
6699
  if (merged.length > 0) next.externalFiles = merged;
6359
6700
  else delete next.externalFiles;
6360
6701
  const touched = new Set(result.portionPaths);
@@ -6443,6 +6784,8 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
6443
6784
  result.sharedFiles.flatMap((f) => [...f.restored, ...f.added]).filter((id) => dropped.has(id))
6444
6785
  )
6445
6786
  ],
6787
+ anchorRetired: result.anchorRetired,
6788
+ agentsMd: agentsMdNews(result.agentsMd, agentsMdBefore, priorPaths),
6446
6789
  // #625 — install 화면(`sharedRow`)이 말하는 "남긴 것" 을 update 도 말한다. 고친 구간과 기록이 없어 모르는 구간은
6447
6790
  // 다른 사실이라 기록(`portions`)으로 가른다 — 어댑터는 기록된 키가 고쳐졌으면 남기고 기록 sha 를 잇는다(계약 ⓑ)
6448
6791
  sharedLeft: result.sharedFiles.filter((f) => f.action === "left" || f.leftAsIs.length + f.kept.length > 0).map((f) => {
@@ -6456,7 +6799,8 @@ function refreshExternalCli(projectDir, harnessRoot, droppedKeys, harnessMcp) {
6456
6799
  kept: [...f.kept],
6457
6800
  ...f.action === "left" ? { left: f.line } : {},
6458
6801
  // #625 — 남긴 구간이 옛 훅 형식이면 현행 Codex 가 무시해 세션 시작 훅이 돌지 않는다(#627) — 화면이 덧붙인다
6459
- ...f.path === ".codex/config.toml" && legacyHookOnDisk(projectDir, f.path) ? { legacyHook: true } : {}
6802
+ ...f.path === ".codex/config.toml" && legacyHookOnDisk(projectDir, f.path) ? { legacyHook: true } : {},
6803
+ ...retiredFeaturesPart(projectDir, f)
6460
6804
  };
6461
6805
  })
6462
6806
  };
@@ -6468,15 +6812,15 @@ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
6468
6812
  if (!existsSync20(target) || !existsSync20(source)) return { updated: 0, backedUp: [] };
6469
6813
  let updated = 0;
6470
6814
  const backedUp = [];
6471
- for (const file of readdirSync2(target)) {
6815
+ for (const file of readdirSync3(target)) {
6472
6816
  if (!file.endsWith(ext)) continue;
6473
- const targetFile = join24(target, file);
6474
- const sourceFile = join24(source, file);
6817
+ const targetFile = join27(target, file);
6818
+ const sourceFile = join27(source, file);
6475
6819
  if (!existsSync20(sourceFile)) continue;
6476
6820
  if (ctx.outside?.skip(targetFile)) continue;
6477
6821
  if (ctx.excluded && isBaselineExcluded(`.claude/${ctx.prefix}/${file}`, ctx.excluded)) continue;
6478
- const next = readFileSync19(sourceFile, "utf8");
6479
- const current = readFileSync19(targetFile, "utf8");
6822
+ const next = readFileSync21(sourceFile, "utf8");
6823
+ const current = readFileSync21(targetFile, "utf8");
6480
6824
  if (current === next) continue;
6481
6825
  if (!isHarnessOwned2(ctx, file, current)) {
6482
6826
  backupFile(targetFile, now);
@@ -6495,31 +6839,31 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
6495
6839
  const skippedLinks = [];
6496
6840
  const foreignOwned = [];
6497
6841
  const pruned = [];
6498
- for (const skill of readdirSync2(sourceDir, { withFileTypes: true })) {
6842
+ for (const skill of readdirSync3(sourceDir, { withFileTypes: true })) {
6499
6843
  if (!skill.isDirectory()) continue;
6500
- const targetSkill = join24(targetDir, skill.name);
6844
+ const targetSkill = join27(targetDir, skill.name);
6501
6845
  if (occupiedByNonDirectory(targetSkill)) {
6502
6846
  skippedLinks.push(skill.name);
6503
6847
  continue;
6504
6848
  }
6505
6849
  if (!existsSync20(targetSkill)) continue;
6506
6850
  if (excludedSkill?.(skill.name)) continue;
6507
- for (const rel of listFilesRecursive(join24(sourceDir, skill.name))) {
6508
- const targetFile = join24(targetSkill, rel);
6851
+ for (const rel of listFilesRecursive(join27(sourceDir, skill.name))) {
6852
+ const targetFile = join27(targetSkill, rel);
6509
6853
  const foreign = foreignOf(`${skill.name}/${rel}`);
6510
6854
  if (foreign !== null) {
6511
6855
  if (!foreignOwned.includes(foreign)) foreignOwned.push(foreign);
6512
6856
  continue;
6513
6857
  }
6514
6858
  if (outside?.skip(targetFile)) continue;
6515
- const next = readFileSync19(join24(sourceDir, skill.name, rel), "utf8");
6859
+ const next = readFileSync21(join27(sourceDir, skill.name, rel), "utf8");
6516
6860
  if (!existsSync20(targetFile)) {
6517
6861
  mkdirSync5(dirname5(targetFile), { recursive: true });
6518
6862
  writeFileSync7(targetFile, next);
6519
6863
  updated++;
6520
6864
  continue;
6521
6865
  }
6522
- const current = readFileSync19(targetFile, "utf8");
6866
+ const current = readFileSync21(targetFile, "utf8");
6523
6867
  if (current === next) continue;
6524
6868
  const recorded2 = baseline.get(`${skill.name}/${rel}`);
6525
6869
  if (recorded2 === void 0 || hashContent(current) !== recorded2) {
@@ -6529,20 +6873,20 @@ function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Da
6529
6873
  writeFileSync7(targetFile, next);
6530
6874
  updated++;
6531
6875
  }
6532
- const bundled = new Set(listFilesRecursive(join24(sourceDir, skill.name)));
6876
+ const bundled = new Set(listFilesRecursive(join27(sourceDir, skill.name)));
6533
6877
  for (const rel of listFilesRecursive(targetSkill)) {
6534
- if (bundled.has(rel) || basename4(rel).includes(".backup-")) continue;
6878
+ if (bundled.has(rel) || basename3(rel).includes(".backup-")) continue;
6535
6879
  if (foreignOf(`${skill.name}/${rel}`) !== null) continue;
6536
- const targetFile = join24(targetSkill, rel);
6880
+ const targetFile = join27(targetSkill, rel);
6537
6881
  if (outside?.skip(targetFile)) continue;
6538
6882
  const recorded2 = baseline.get(`${skill.name}/${rel}`);
6539
- if (recorded2 === void 0 || hashContent(readFileSync19(targetFile, "utf8")) !== recorded2) {
6883
+ if (recorded2 === void 0 || hashContent(readFileSync21(targetFile, "utf8")) !== recorded2) {
6540
6884
  backupFile(targetFile, now);
6541
6885
  backedUp.push(`${skill.name}/${rel}`);
6542
6886
  }
6543
6887
  unlinkSync2(targetFile);
6544
6888
  pruned.push(`${skill.name}/${rel}`);
6545
- for (let d2 = dirname5(targetFile); d2 !== targetSkill && readdirSync2(d2).length === 0; d2 = dirname5(d2))
6889
+ for (let d2 = dirname5(targetFile); d2 !== targetSkill && readdirSync3(d2).length === 0; d2 = dirname5(d2))
6546
6890
  rmdirSync(d2);
6547
6891
  }
6548
6892
  }
@@ -6598,15 +6942,15 @@ function refreshSkillBaseline(projectDir, templatesDir, outside) {
6598
6942
  function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
6599
6943
  if (!existsSync20(target) || !existsSync20(source)) return [];
6600
6944
  const removed = [];
6601
- for (const file of readdirSync2(target)) {
6945
+ for (const file of readdirSync3(target)) {
6602
6946
  if (!file.endsWith(ext)) continue;
6603
- const sourceFile = join24(source, file);
6947
+ const sourceFile = join27(source, file);
6604
6948
  if (!existsSync20(sourceFile)) {
6605
- const targetFile = join24(target, file);
6949
+ const targetFile = join27(target, file);
6606
6950
  if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
6607
6951
  if (ctx.outside?.skip(targetFile)) continue;
6608
6952
  try {
6609
- const current = readFileSync19(targetFile, "utf8");
6953
+ const current = readFileSync21(targetFile, "utf8");
6610
6954
  if (!isHarnessOwned2(ctx, file, current)) backupFile(targetFile, now);
6611
6955
  unlinkSync2(targetFile);
6612
6956
  removed.push(file);
@@ -6617,7 +6961,7 @@ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()
6617
6961
  return removed;
6618
6962
  }
6619
6963
  function retireMcpAllowlist(projectDir, now = /* @__PURE__ */ new Date()) {
6620
- const target = join24(projectDir, ".mcp-allowlist");
6964
+ const target = join27(projectDir, ".mcp-allowlist");
6621
6965
  if (!existsSync20(target)) return null;
6622
6966
  try {
6623
6967
  const backup = backupFile(target, now);
@@ -6633,12 +6977,12 @@ function refreshNeutralHelpers(projectDir, templatesDir, report) {
6633
6977
  for (const entry of buildManifest(spec)) {
6634
6978
  if (entry.type !== "file" || !isCliNeutralTarget(entry.target)) continue;
6635
6979
  if (!entry.applies(spec)) continue;
6636
- const abs = join24(projectDir, entry.target);
6637
- const source = join24(templatesDir, entry.source);
6980
+ const abs = join27(projectDir, entry.target);
6981
+ const source = join27(templatesDir, entry.source);
6638
6982
  if (!existsSync20(source)) continue;
6639
6983
  let st2;
6640
6984
  try {
6641
- st2 = lstatSync3(abs);
6985
+ st2 = lstatSync4(abs);
6642
6986
  } catch {
6643
6987
  continue;
6644
6988
  }
@@ -6649,8 +6993,8 @@ function refreshNeutralHelpers(projectDir, templatesDir, report) {
6649
6993
  if (!st2.isFile()) continue;
6650
6994
  const log2 = readInstallLog(projectDir);
6651
6995
  const recorded2 = (log2?.externalFiles ?? []).find((f) => f.path === entry.target);
6652
- const diskSha = hashContent(readFileSync19(abs, "utf8"));
6653
- const freshContent = readFileSync19(source, "utf8");
6996
+ const diskSha = hashContent(readFileSync21(abs, "utf8"));
6997
+ const freshContent = readFileSync21(source, "utf8");
6654
6998
  const freshSha = hashContent(freshContent);
6655
6999
  if (diskSha === freshSha) {
6656
7000
  if (recorded2?.sha256 !== freshSha) refreshed.set(entry.target, freshSha);
@@ -6690,7 +7034,7 @@ function dropHealedHookPortions(projectDir, healedFiles) {
6690
7034
  const log = readInstallLog(projectDir);
6691
7035
  if (log === null) return;
6692
7036
  const portions = (log.portions ?? []).filter(
6693
- (p2) => !(p2.path === join24(".claude", "settings.json") && isHealedKey(p2.key))
7037
+ (p2) => !(p2.path === join27(".claude", "settings.json") && isHealedKey(p2.key))
6694
7038
  );
6695
7039
  if (portions.length > 0) log.portions = portions;
6696
7040
  else delete log.portions;
@@ -6704,11 +7048,11 @@ function dropHealedHookPortions(projectDir, healedFiles) {
6704
7048
  function runInstall(ctx) {
6705
7049
  const { harnessRoot, projectDir } = ctx;
6706
7050
  const mode = ctx.mode ?? "fresh";
6707
- const templatesDir = join25(harnessRoot, "templates");
7051
+ const templatesDir = join28(harnessRoot, "templates");
6708
7052
  if (!existsSync21(templatesDir)) {
6709
7053
  throw new Error(`Templates dir not found: ${templatesDir}`);
6710
7054
  }
6711
- const claudeDir = join25(projectDir, ".claude");
7055
+ const claudeDir = join28(projectDir, ".claude");
6712
7056
  const previousLog = readInstallLog(projectDir);
6713
7057
  const legacyAnchor = mode === "update" ? null : legacyAnchorTrace(projectDir, ctx.spec);
6714
7058
  if (mode === "update" && previousLog === null) {
@@ -6737,10 +7081,10 @@ function runInstall(ctx) {
6737
7081
  const manifestSpec = buildManifestSpec(spec);
6738
7082
  const baselineExcluded = new Set(spec.baselineExclude ?? []);
6739
7083
  if (spec.cli.includes("claude")) {
6740
- const settingsPath = join25(projectDir, ".claude", "settings.json");
7084
+ const settingsPath = join28(projectDir, ".claude", "settings.json");
6741
7085
  if (existsSync21(settingsPath)) {
6742
7086
  try {
6743
- JSON.parse(readFileSync20(settingsPath, "utf8"));
7087
+ JSON.parse(readFileSync22(settingsPath, "utf8"));
6744
7088
  } catch {
6745
7089
  throw new Error(
6746
7090
  `${relative3(process.cwd(), settingsPath)} is not valid JSON \u2014 left untouched, so no hooks were wired. Fix or delete that file, then run the install again`
@@ -6830,9 +7174,16 @@ function runInstallStages(ctx, args) {
6830
7174
  rules: resolveRules(manifestSpec).filter(
6831
7175
  (r) => !isBaselineExcluded(`.claude/rules/${r}.md`, baselineExcluded)
6832
7176
  ),
7177
+ // v26.165.0 NOTE-5 — `AGENTS.md` 가 이번 선택 밖의 기록 CLI 몫까지 다시 렌더될 때의 룰: 기록 트랙 ∪ 이번 트랙(update 와 같은 식)
7178
+ cumulativeRules: resolveRules({
7179
+ ...manifestSpec,
7180
+ tracks: [.../* @__PURE__ */ new Set([...(previousLog?.spec.tracks ?? []).filter(isTrack), ...spec.tracks])]
7181
+ }).filter((r) => !isBaselineExcluded(`.claude/rules/${r}.md`, baselineExcluded)),
6833
7182
  // #568 — Codex · OpenCode 의 MCP 서버는 `.mcp.json` 에 쓴 것과 같은 값이다.
6834
7183
  mcp: harnessMcp,
6835
7184
  previousExternal: previousLog?.externalFiles ?? [],
7185
+ // #642 (ADR-096 D2) — CLI 집합은 누적이다: 기록된 CLI 도 `AGENTS.md` 집합에 들어간다(`--reinstall` 도 누적)
7186
+ recordedClis: previousLog === null ? [] : installedClis(previousLog),
6836
7187
  // ADR-097 결정 2 — 범위 조건 없이 `--with-codex-trust` 하나로 정한다.
6837
7188
  codexTrust: spec.options.withCodexTrust,
6838
7189
  // #551 R2 — `.codex/config.toml` · `opencode.json` · 첫 접촉 `AGENTS.md` 의 몫도 `.mcp.json` 과 같은 왕복을
@@ -6859,9 +7210,14 @@ function runInstallStages(ctx, args) {
6859
7210
  excluded,
6860
7211
  outside: reclaimOutside
6861
7212
  });
6862
- const forgotten = new Set(outOfTrack.forget);
7213
+ const anchorRetired = cliTransforms.anchorRetired;
7214
+ const forgotten = /* @__PURE__ */ new Set([
7215
+ ...outOfTrack.forget,
7216
+ ...anchorRetired?.forget === true ? [anchorRetired.path] : []
7217
+ ]);
6863
7218
  const logBase = previousLog === null || forgotten.size === 0 ? previousLog : {
6864
7219
  ...previousLog,
7220
+ spec: { ...previousLog.spec, clis: [...installedClis(previousLog)] },
6865
7221
  externalFiles: (previousLog.externalFiles ?? []).filter((f) => !forgotten.has(f.path))
6866
7222
  };
6867
7223
  stage.envExampleCreated = writeEnvExample(projectDir, spec.tracks);
@@ -7002,7 +7358,7 @@ function releasedThisRun(args) {
7002
7358
  }
7003
7359
  if (id.startsWith(BASELINE_PREFIX)) {
7004
7360
  const again2 = manifest.some(
7005
- (e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync21(join25(projectDir, e2.target))
7361
+ (e2) => e2.applies(manifestSpec) && classifyBaselineTarget(e2.target)?.id === id && existsSync21(join28(projectDir, e2.target))
7006
7362
  );
7007
7363
  return { id, again: again2, tail: "the harness manages it again" };
7008
7364
  }
@@ -7011,7 +7367,7 @@ function releasedThisRun(args) {
7011
7367
  options: spec.options,
7012
7368
  ...spec.userOverride ? { userOverride: spec.userOverride } : {}
7013
7369
  });
7014
- const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join25(projectDir, d2, id)));
7370
+ const recorded2 = (previousLog?.assets.some((a) => a.id === id) ?? false) || [".claude/skills", ".agents/skills"].some((d2) => existsSync21(join28(projectDir, d2, id)));
7015
7371
  return {
7016
7372
  id,
7017
7373
  again,
@@ -7023,10 +7379,10 @@ function pendingKeyExcludes(projectDir, keyExclude, touched) {
7023
7379
  const done = new Set(touched);
7024
7380
  return keyExclude.flatMap((id) => {
7025
7381
  const path = sharedPathOfKeyId(id);
7026
- if (path === null || done.has(path) || !existsSync21(join25(projectDir, path))) return [];
7382
+ if (path === null || done.has(path) || !existsSync21(join28(projectDir, path))) return [];
7027
7383
  const name = adapterFor(path);
7028
7384
  const keys = [...excludedKeys(path, [id])];
7029
- const present2 = name === null ? null : ADAPTERS[name].read(readFileSync20(join25(projectDir, path), "utf8"), keys, projectDir);
7385
+ const present2 = name === null ? null : ADAPTERS[name].read(readFileSync22(join28(projectDir, path), "utf8"), keys, projectDir);
7030
7386
  return present2 !== null && present2.size === 0 ? [] : [{ id, path }];
7031
7387
  });
7032
7388
  }
@@ -7070,7 +7426,7 @@ function installCliNeutralAssets(manifestSpec, templatesDir, baselineExcluded, w
7070
7426
  continue;
7071
7427
  }
7072
7428
  if (!isCliNeutralTarget(entry.target)) continue;
7073
- const source = join25(templatesDir, entry.source);
7429
+ const source = join28(templatesDir, entry.source);
7074
7430
  if (!existsSync21(source)) {
7075
7431
  result.skipped += 1;
7076
7432
  continue;
@@ -7091,13 +7447,13 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
7091
7447
  if (!entry.applies(manifestSpec)) {
7092
7448
  continue;
7093
7449
  }
7094
- const target = join25(projectDir, entry.target);
7450
+ const target = join28(projectDir, entry.target);
7095
7451
  if (isBaselineExcluded(entry.target, baselineExcluded)) {
7096
7452
  result.excluded.push(entry.target);
7097
7453
  if (existsSync21(target)) result.excludedOnDisk.push(entry.target);
7098
7454
  continue;
7099
7455
  }
7100
- const source = join25(templatesDir, entry.source);
7456
+ const source = join28(templatesDir, entry.source);
7101
7457
  if (!existsSync21(source)) {
7102
7458
  result.skipped += 1;
7103
7459
  continue;
@@ -7128,18 +7484,18 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
7128
7484
  if (!result.foreignOwned.includes(slot)) result.foreignOwned.push(slot);
7129
7485
  continue;
7130
7486
  }
7131
- if (writer.skipOutside(join25(projectDir, entry.target, rel))) {
7487
+ if (writer.skipOutside(join28(projectDir, entry.target, rel))) {
7132
7488
  leftOutside += 1;
7133
7489
  continue;
7134
7490
  }
7135
- writer.harness(`${entry.target}/${rel}`, { source: join25(source, rel) });
7491
+ writer.harness(`${entry.target}/${rel}`, { source: join28(source, rel) });
7136
7492
  }
7137
7493
  if (leftOutside > 0) continue;
7138
7494
  result.dirsCopied += 1;
7139
7495
  }
7140
7496
  accumulateCategory(result.categories, entry);
7141
7497
  }
7142
- const hookDir = join25(projectDir, ".claude/hooks");
7498
+ const hookDir = join28(projectDir, ".claude/hooks");
7143
7499
  if (existsSync21(hookDir)) {
7144
7500
  chmodHooksSync(hookDir, projectDir);
7145
7501
  }
@@ -7156,12 +7512,12 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
7156
7512
  if (settingsSource !== null) {
7157
7513
  writeSettingsPortion(writer, settingsSource, projectDir, previousLog, (script) => {
7158
7514
  const target = `${HOOKS_PREFIX}${script}`;
7159
- return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) && existsSync21(join25(projectDir, target));
7515
+ return manifest.some((e2) => e2.target === target && e2.applies(manifestSpec)) && !isBaselineExcluded(target, baselineExcluded) && existsSync21(join28(projectDir, target));
7160
7516
  });
7161
7517
  result.keptHookRefs.push(
7162
7518
  ...findStaleHookRefs(
7163
- join25(projectDir, ".claude", "settings.json"),
7164
- join25(projectDir, ".claude")
7519
+ join28(projectDir, ".claude", "settings.json"),
7520
+ join28(projectDir, ".claude")
7165
7521
  )
7166
7522
  );
7167
7523
  }
@@ -7171,7 +7527,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
7171
7527
  // ADR-085 · update(`upsertRootImport`)와 같은 판정 — 실제로 깔린 스킬만 안내한다. 밖 링크라 쓰지 않은
7172
7528
  // 스킬(ADR-098)을 "installed" 라 적으면 다음 update 가 그 절을 걷어 내 두 동작이 서로 반대로 쓴다.
7173
7529
  (manifestSpec.selectedInternalSkills ?? []).filter(
7174
- (id) => existsSync21(join25(projectDir, ".claude", "skills", id))
7530
+ (id) => existsSync21(join28(projectDir, ".claude", "skills", id))
7175
7531
  ),
7176
7532
  harnessRoot,
7177
7533
  writer
@@ -7187,7 +7543,7 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, baselineE
7187
7543
  function writeSettingsPortion(writer, source, projectDir, previousLog, hookInstalled) {
7188
7544
  return writeSettingsShared(
7189
7545
  writer,
7190
- readFileSync20(source, "utf8"),
7546
+ readFileSync22(source, "utf8"),
7191
7547
  projectDir,
7192
7548
  previousLog,
7193
7549
  hookInstalled
@@ -7342,7 +7698,10 @@ function recordInterruptedInstall(ctx, error, run) {
7342
7698
  }
7343
7699
  }
7344
7700
  function registeredTrust(codexOptIn, projectDir) {
7345
- return codexOptIn?.trustEntry.status === "registered" && codexOptIn.trustEntry.configPath ? { configPath: codexOptIn.trustEntry.configPath, projectDir } : void 0;
7701
+ return codexOptIn?.trustEntry.status === "registered" && codexOptIn.trustEntry.configPath ? {
7702
+ configPath: codexOptIn.trustEntry.configPath,
7703
+ projectDir: codexOptIn.trustEntry.key ?? projectDir
7704
+ } : void 0;
7346
7705
  }
7347
7706
  function writtenSoFar(ledger, cliFiles, rootFiles, rootImportWritten) {
7348
7707
  const files = /* @__PURE__ */ new Set([
@@ -7440,12 +7799,12 @@ function accumulateCategory(cats, entry) {
7440
7799
  }
7441
7800
  }
7442
7801
  function writeRootClaudeMd(projectDir, tracks, continuousSkills, harnessRoot, writer) {
7443
- const target = join25(projectDir, "CLAUDE.md");
7802
+ const target = join28(projectDir, "CLAUDE.md");
7444
7803
  if (writer.skipOutside(target)) return null;
7445
- const existing = existsSync21(target) ? readFileSync20(target, "utf-8") : null;
7804
+ const existing = existsSync21(target) ? readFileSync22(target, "utf-8") : null;
7446
7805
  const seeded = existing === null ? seedRootClaudeProjectContext(projectDir, harnessRoot) : null;
7447
7806
  const content = upsertHarnessImport(existing, {
7448
- projectName: basename5(projectDir),
7807
+ projectName: basename4(projectDir),
7449
7808
  tracks,
7450
7809
  continuousSkills,
7451
7810
  ...seeded === null ? {} : { projectContext: seeded }
@@ -7470,14 +7829,14 @@ function chmodHooksSync(hookDir, projectDir) {
7470
7829
  }
7471
7830
  }
7472
7831
  function listHookFiles(hookDir) {
7473
- return readdirSync3(hookDir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".sh")).map((e2) => resolve(hookDir, e2.name));
7832
+ return readdirSync4(hookDir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".sh")).map((e2) => resolve(hookDir, e2.name));
7474
7833
  }
7475
7834
 
7476
7835
  // src/commands/install-render.ts
7477
7836
  init_esm_shims();
7478
7837
  import { homedir as homedir3 } from "os";
7479
- import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync21 } from "fs";
7480
- import { join as join27, relative as relative4 } from "path";
7838
+ import { existsSync as existsSync23, readdirSync as readdirSync5, readFileSync as readFileSync24 } from "fs";
7839
+ import { join as join30, relative as relative4 } from "path";
7481
7840
 
7482
7841
  // src/preset-recommend.ts
7483
7842
  init_esm_shims();
@@ -7513,10 +7872,10 @@ function groupAssetsByCategory(assetIds) {
7513
7872
 
7514
7873
  // src/resident-entries.ts
7515
7874
  init_esm_shims();
7516
- import { existsSync as existsSync22 } from "fs";
7517
- import { join as join26 } from "path";
7875
+ import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
7876
+ import { basename as basename5, join as join29 } from "path";
7518
7877
  function residentEntries(spec) {
7519
- const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync22(join26(spec.projectDir, d2, id)));
7878
+ const skillThere = (id) => INTERNAL_BUNDLED_SKILL_IDS.includes(id) && [".claude/skills", ".agents/skills"].some((d2) => existsSync22(join29(spec.projectDir, d2, id)));
7520
7879
  const forceExclude = (spec.userOverride?.forceExclude ?? []).filter((id) => !skillThere(id));
7521
7880
  const assetSpec = buildManifestSpec(
7522
7881
  spec.userOverride ? { ...spec, userOverride: { ...spec.userOverride, forceExclude } } : spec
@@ -7540,25 +7899,44 @@ function residentEntries(spec) {
7540
7899
  });
7541
7900
  }
7542
7901
  function residentCostFor(spec, entries = residentEntries(spec), contextFiles) {
7543
- return residentCost(entries, void 0, spec.cli, contextFiles);
7902
+ if (contextFiles !== void 0) return residentCost(entries, void 0, spec.cli, contextFiles);
7903
+ const cost = residentCost(entries, void 0, spec.cli, plannedClaudeContext(spec));
7904
+ if (anchorSurface(spec.cli).scaffold === "agents-md" && existsSync22(join29(spec.projectDir, "AGENTS.md"))) {
7905
+ return makeResidentCost({
7906
+ ...cost,
7907
+ contextNote: "planned, not counting text already in AGENTS.md"
7908
+ });
7909
+ }
7910
+ return cost;
7911
+ }
7912
+ function plannedClaudeContext(spec) {
7913
+ if (!spec.cli.includes("claude")) return void 0;
7914
+ const root = resolveBundleRoot();
7915
+ const rootClaude = join29(spec.projectDir, "CLAUDE.md");
7916
+ const existing = existsSync22(rootClaude) ? readFileSync23(rootClaude, "utf-8") : null;
7917
+ const seeded = existing === null ? seedRootClaudeProjectContext(spec.projectDir, root) : null;
7918
+ if (existing === null && seeded === null) return void 0;
7919
+ const manifestSpec = buildManifestSpec(spec);
7920
+ const text = upsertHarnessImport(existing, {
7921
+ projectName: basename5(spec.projectDir),
7922
+ tracks: manifestSpec.tracks,
7923
+ continuousSkills: manifestSpec.selectedInternalSkills ?? [],
7924
+ ...seeded === null ? {} : { projectContext: seeded }
7925
+ });
7926
+ return [join29(root, "templates", "CLAUDE.md"), { text }];
7544
7927
  }
7545
7928
  function onDiskTargets(spec, assetSpec) {
7546
7929
  const out = /* @__PURE__ */ new Set();
7547
7930
  for (const e2 of buildManifest(assetSpec)) {
7548
7931
  if (!e2.applies(assetSpec)) continue;
7549
7932
  const t = classifyBaselineTarget(e2.target);
7550
- if (t !== null && existsSync22(join26(spec.projectDir, e2.target))) out.add(t.id);
7933
+ if (t !== null && existsSync22(join29(spec.projectDir, e2.target))) out.add(t.id);
7551
7934
  }
7552
7935
  return out;
7553
7936
  }
7554
7937
 
7555
7938
  // src/commands/install-render.ts
7556
- var CLI_SUMMARY_LABELS = {
7557
- claude: "Claude",
7558
- codex: "Codex",
7559
- opencode: "OpenCode",
7560
- antigravity: "Antigravity"
7561
- };
7939
+ var CLI_SUMMARY_LABELS = CLI_DISPLAY_NAMES;
7562
7940
  var LEGACY_ANCHOR_NOTE = `legacy anchor \xB7 no longer updated \u2014 content now in ${HARNESS_ANCHOR_FILE}; delete it when you no longer need it`;
7563
7941
  function renderInstallHeader(log, spec, mode) {
7564
7942
  const headerLabel = mode === "update" ? "uzys-agent-harness \xB7 update" : mode === "add" ? "uzys-agent-harness \xB7 add" : mode === "reinstall" ? "uzys-agent-harness \xB7 reinstall" : "uzys-agent-harness \xB7 install";
@@ -7667,7 +8045,10 @@ function renderUpdateSummary(log, spec, report) {
7667
8045
  log(infoRow("ROLLBACK", `rm -rf .claude && mv ${shellQuotePath(report.backup)} .claude`));
7668
8046
  }
7669
8047
  const cost = formatResidentCostLine(
7670
- residentCostFor(spec, residentEntriesOnDisk(spec), contextFilesOnDisk(spec)),
8048
+ rulesInsideAgentsMd(
8049
+ spec,
8050
+ residentCostFor(spec, residentEntriesOnDisk(spec), contextFilesOnDisk(spec))
8051
+ ),
7671
8052
  summarizeContextCost(finalSelectedAssets(spec.tracks, spec.userOverride)).unmeasuredCount
7672
8053
  );
7673
8054
  if (cost) log(infoRow("CONTEXT", cost));
@@ -7695,44 +8076,66 @@ function renderUpdateSummary(log, spec, report) {
7695
8076
  }
7696
8077
  function residentEntriesOnDisk(spec) {
7697
8078
  const planned = residentEntries(spec);
7698
- if (!spec.cli.includes("claude")) {
7699
- return planned.filter((e2) => !e2.target.startsWith(".claude/agents/"));
7700
- }
7701
- const mdFiles = (kind) => {
7702
- const dir = join27(spec.projectDir, ".claude", kind);
7703
- return existsSync23(dir) ? readdirSync4(dir, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md")).map((e2) => ({
8079
+ const hasClaude = spec.cli.includes("claude");
8080
+ const surface = anchorSurface(spec.cli).scaffold;
8081
+ const mdFiles = (dir, kind, skip) => {
8082
+ const abs = join30(spec.projectDir, dir);
8083
+ return existsSync23(abs) ? readdirSync5(abs, { withFileTypes: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".md") && e2.name !== skip).map((e2) => ({
7704
8084
  source: `${kind}/${e2.name}`,
8085
+ // `target` 은 `residentCost` 의 분류 키다 — 실제 자리는 `file` 이 말한다.
7705
8086
  target: `.claude/${kind}/${e2.name}`,
7706
8087
  // 배포판이 아니라 **이 프로젝트의 파일**을 잰다 — 사용자가 고친 본문도,
7707
8088
  // 배포판에 더는 없는 파일도 실제로 상주하는 것은 이쪽이다.
7708
- file: join27(dir, e2.name)
8089
+ file: join30(abs, e2.name)
7709
8090
  })) : [];
7710
8091
  };
7711
- const skills = planned.filter((e2) => e2.target.startsWith(".claude/skills/")).map((e2) => ({ ...e2, file: join27(spec.projectDir, e2.target) })).filter((e2) => existsSync23(e2.file));
8092
+ const rules = surface === "claude" ? mdFiles(join30(".claude", "rules"), "rules") : rulesInline(spec.cli) ? planned.filter((e2) => e2.target.startsWith(".claude/rules/")) : mdFiles(join30(".agents", "rules"), "rules", "uzys-harness.md");
8093
+ const slot = hasClaude ? join30(".claude", "skills") : join30(".agents", "skills");
8094
+ const skills = planned.filter((e2) => e2.target.startsWith(".claude/skills/")).map((e2) => ({
8095
+ ...e2,
8096
+ file: join30(spec.projectDir, slot, e2.target.slice(".claude/skills/".length))
8097
+ })).filter((e2) => existsSync23(e2.file));
8098
+ const agents = hasClaude ? mdFiles(join30(".claude", "agents"), "agents") : [];
7712
8099
  return [
7713
8100
  ...planned.filter(
7714
8101
  (e2) => !e2.target.startsWith(".claude/rules/") && !e2.target.startsWith(".claude/agents/") && !e2.target.startsWith(".claude/skills/")
7715
8102
  ),
7716
- ...mdFiles("rules"),
8103
+ ...rules,
7717
8104
  ...skills,
7718
- ...mdFiles("agents")
8105
+ ...agents
7719
8106
  ];
7720
8107
  }
7721
8108
  function contextFilesOnDisk(spec) {
7722
- if (!spec.cli.includes("claude")) return void 0;
7723
- return [HARNESS_ANCHOR_FILE, "CLAUDE.md", join27(".claude", "CLAUDE.md")].map(
7724
- (f) => join27(spec.projectDir, f)
7725
- );
8109
+ const surface = anchorSurface(spec.cli).scaffold;
8110
+ const files = surface === "claude" ? [HARNESS_ANCHOR_FILE, "CLAUDE.md", join30(".claude", "CLAUDE.md")] : ["AGENTS.md"];
8111
+ return files.map((f) => join30(spec.projectDir, f));
8112
+ }
8113
+ function rulesInsideAgentsMd(spec, cost) {
8114
+ if (anchorSurface(spec.cli).scaffold !== "agents-md" || !rulesInline(spec.cli)) return cost;
8115
+ const rendered = spec.updateOnly === void 0 || spec.updateOnly.includes("external");
8116
+ return makeResidentCost({
8117
+ ...cost,
8118
+ rules: 0,
8119
+ rulesInline: rendered ? "rendered" : "planned",
8120
+ contextNote: "whole file, incl. your text"
8121
+ });
8122
+ }
8123
+ function rulesInline(cli) {
8124
+ return cli.includes("codex") || cli.includes("opencode");
7726
8125
  }
7727
8126
  function renderCliArtifacts(log, spec, report) {
7728
- const hasArtifacts = Boolean(report.codex || report.opencode || report.antigravity);
7729
- const cliSelected = targetsInclude(spec.cli, "codex") || targetsInclude(spec.cli, "opencode") || targetsInclude(spec.cli, "antigravity");
8127
+ const agentsReport = report.agentsMd ?? null;
8128
+ const hasArtifacts = Boolean(
8129
+ report.codex || report.opencode || report.antigravity || agentsReport
8130
+ );
8131
+ const shown = [...spec.cli, ...agentsReport?.clis ?? []];
8132
+ const cliSelected = targetsInclude(shown, "codex") || targetsInclude(shown, "opencode") || targetsInclude(shown, "antigravity");
7730
8133
  if (!hasArtifacts || !cliSelected) {
7731
8134
  return;
7732
8135
  }
7733
- log(unifiedSection(formatCliPhaseTitle(spec.cli)));
8136
+ log(unifiedSection(formatCliPhaseTitle(shown)));
7734
8137
  log("");
7735
- const agentsMd = report.opencode?.agentsMd ?? report.codex?.agentsMd ?? null;
8138
+ const agentsMd = agentsReport?.agentsMd ?? null;
7736
8139
  const agentsBlock = agentsMd?.model === "block" ? agentsMd.shared : null;
7737
8140
  if (agentsBlock) {
7738
8141
  const row = sharedRow(
@@ -7740,12 +8143,15 @@ function renderCliArtifacts(log, spec, report) {
7740
8143
  harnessKeysInFile(agentsBlock).length > 0 ? "one harness block at the end (your text kept as-is)" : "no harness block in the file (your text kept as-is)"
7741
8144
  );
7742
8145
  if (row) log(row);
7743
- } else if (report.codex && report.opencode) {
7744
- log(assetRow("success", "AGENTS.md", "shared (Codex + OpenCode)"));
7745
- } else if (report.codex || report.opencode) {
7746
- log(assetRow("success", "AGENTS.md", `from ${HARNESS_ANCHOR_FILE}`));
7747
- }
7748
- const agentsSeededFrom = report.codex?.agentsMdSeededFrom ?? report.opencode?.agentsMdSeededFrom ?? null;
8146
+ } else if (agentsReport && agentsReport.clis.length > 1) {
8147
+ const names = agentsReport.clis.map((c3) => CLI_DISPLAY_NAMES[c3]).join(" \xB7 ");
8148
+ log(assetRow("success", "AGENTS.md", `shared (${names})`));
8149
+ } else if (agentsReport) {
8150
+ const only = agentsReport.clis[0];
8151
+ const who = only === void 0 ? "" : ` (${CLI_DISPLAY_NAMES[only]})`;
8152
+ log(assetRow("success", "AGENTS.md", `from ${HARNESS_ANCHOR_FILE}${who}`));
8153
+ }
8154
+ const agentsSeededFrom = agentsReport?.agentsMdSeededFrom ?? null;
7749
8155
  if (agentsSeededFrom) {
7750
8156
  log(
7751
8157
  assetRow(
@@ -7758,6 +8164,8 @@ function renderCliArtifacts(log, spec, report) {
7758
8164
  if (report.codex) {
7759
8165
  const configRow = report.codex.configToml ? sharedRow(report.codex.configToml, configRegionsPart(report.codex.configToml)) : assetRow("success", ".codex/config.toml", "settings + [mcp_servers.*]");
7760
8166
  if (configRow) log(configRow);
8167
+ const retired2 = report.codex.configToml ? retiredFeaturesLeftBehind(spec.projectDir, report.codex.configToml) : [];
8168
+ if (retired2.length > 0) log(retiredFeaturesRow(".codex/config.toml", retired2));
7761
8169
  log(assetRow("success", ".codex/hooks/", `${report.codex.hookFiles.length} files`));
7762
8170
  if (report.codex.skillFiles.length > 0) {
7763
8171
  log(
@@ -7770,7 +8178,7 @@ function renderCliArtifacts(log, spec, report) {
7770
8178
  }
7771
8179
  if (report.codexOptIn?.trustEntry.enabled) {
7772
8180
  const trust = report.codexOptIn.trustEntry;
7773
- const kind = trust.status === "error" || trust.status === "unreadable" ? "skip" : "success";
8181
+ const kind = trust.status === "error" || trust.status === "unreadable" || trust.status === "left" ? "skip" : "success";
7774
8182
  const meta = trust.status === "registered" ? '[projects."<dir>"] trust_level="trusted"' : trust.status === "already-present" ? "already present" : trust.message ?? "error";
7775
8183
  log(assetRow(kind, "~/.codex/config.toml trust entry", meta));
7776
8184
  }
@@ -7799,22 +8207,20 @@ function renderCliArtifacts(log, spec, report) {
7799
8207
  }
7800
8208
  }
7801
8209
  if (report.antigravity) {
7802
- const anchor = report.antigravity.rulesFile;
7803
8210
  const rules = report.antigravity.harnessRuleFiles.length;
7804
- const total = (anchor ? 1 : 0) + rules;
7805
- if (total > 0) {
7806
- const parts = [
7807
- ...anchor ? [`uzys-harness.md from ${HARNESS_ANCHOR_FILE}`] : [],
7808
- ...rules > 0 ? [`${rules} harness rule${rules === 1 ? "" : "s"}`] : []
7809
- ];
8211
+ if (rules > 0) {
7810
8212
  log(
7811
8213
  assetRow(
7812
8214
  "success",
7813
8215
  ".agents/rules/",
7814
- `${total} file${total === 1 ? "" : "s"} \xB7 ${parts.join(" + ")}`
8216
+ `${rules} harness rule${rules === 1 ? "" : "s"} (with Antigravity trigger frontmatter)`
7815
8217
  )
7816
8218
  );
7817
8219
  }
8220
+ }
8221
+ const retired = anchorRetireRow(report.anchorRetired ?? null);
8222
+ if (retired) log(retired);
8223
+ if (report.antigravity) {
7818
8224
  if (report.antigravity.skillFiles.length > 0) {
7819
8225
  log(
7820
8226
  assetRow(
@@ -7827,6 +8233,21 @@ function renderCliArtifacts(log, spec, report) {
7827
8233
  }
7828
8234
  log("");
7829
8235
  }
8236
+ function agentsMdNewsRow(n) {
8237
+ if (n === void 0) return null;
8238
+ const names = n.clis.map((c3) => CLI_DISPLAY_NAMES[c3]).join(" \xB7 ");
8239
+ const seeded = n.seededFrom === null ? "" : `Project Context seeded from ${n.seededFrom}`;
8240
+ if (!n.created) return seeded === "" ? null : assetRow("success", "AGENTS.md", seeded);
8241
+ return assetRow(
8242
+ "success",
8243
+ "AGENTS.md",
8244
+ `created (${names})${seeded === "" ? "" : ` \xB7 ${seeded}`}`
8245
+ );
8246
+ }
8247
+ function anchorRetireRow(r) {
8248
+ if (r === null || r.line === "") return null;
8249
+ return assetRow(r.action === "removed" ? "success" : "skip", r.path, r.line);
8250
+ }
7830
8251
  function sharedRow(r, part) {
7831
8252
  if (r.action === "skipped") return null;
7832
8253
  if (r.action === "left") return assetRow("skip", r.path, `left \u2014 ${r.line}`);
@@ -7839,6 +8260,10 @@ function sharedRow(r, part) {
7839
8260
  const out = excludedKeyParts(r).map((p2) => ` \xB7 ${p2}`).join("");
7840
8261
  return assetRow("success", r.path, `${verb} \xB7 ${part}${kept}${left}${restored}${out}`);
7841
8262
  }
8263
+ function retiredFeaturesRow(path, keys) {
8264
+ const meta = `[features] ${keys.join(" \xB7 ")} make${keys.length === 1 ? "s" : ""} Codex warn at every start ("deprecated" / "ignoring unrecognized configuration") \u2014 delete those lines; if they sit inside the harness region, delete the whole region and run update`;
8265
+ return ` ${c.yellow(symbol.warn)} ${path.padEnd(40, " ")} ${c.dim(meta)}`;
8266
+ }
7842
8267
  function sharedLeftRow(f) {
7843
8268
  const legacy = f.legacyHook === true ? " \xB7 its [[hooks.session_start]] is the old format \u2014 current Codex ignores it, so the session-start hook does not run" : "";
7844
8269
  if (f.left !== void 0) return assetRow("skip", f.path, `left \u2014 ${f.left}${legacy}`);
@@ -7937,10 +8362,10 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7937
8362
  const codexTrusted = report.codexOptIn?.trustEntry.status === "registered" || report.codexOptIn?.trustEntry.status === "already-present";
7938
8363
  const alreadyTrustedGlobally = (() => {
7939
8364
  try {
7940
- const home = process.env.CODEX_HOME ?? join27(homedir3(), ".codex");
7941
- const configPath = join27(home, "config.toml");
8365
+ const home = process.env.CODEX_HOME ?? join30(homedir3(), ".codex");
8366
+ const configPath = join30(home, "config.toml");
7942
8367
  if (!existsSync23(configPath)) return false;
7943
- return hasTrustEntry(readFileSync21(configPath, "utf8"), spec.projectDir);
8368
+ return hasTrustEntry(readFileSync24(configPath, "utf8"), spec.projectDir);
7944
8369
  } catch {
7945
8370
  return false;
7946
8371
  }
@@ -7952,7 +8377,9 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7952
8377
  `${c.bold("Codex")} turns on .codex/config.toml (MCP \xB7 hooks \xB7 sandbox \xB7 approval) only after you trust this folder:`
7953
8378
  )
7954
8379
  );
7955
- const headless = report.codexOptIn?.trustEntry.status === "unreadable" ? "headless: fix ~/.codex/config.toml (not valid TOML), then run with --with-codex-trust again" : "headless: agent-harness install \u2026 --with-codex-trust";
8380
+ const trust = report.codexOptIn?.trustEntry;
8381
+ const entry = `[projects.${tomlBasicString(trust?.key ?? spec.projectDir)}]`;
8382
+ const headless = trust?.status === "unreadable" ? "headless: fix ~/.codex/config.toml (not valid TOML), then run with --with-codex-trust again" : trust?.status === "error" ? `headless: add ${entry} trust_level = "trusted" to ~/.codex/config.toml by hand (the harness could not append it safely)` : trust?.status === "left" ? `headless: ~/.codex/config.toml already has ${entry} with a trust_level other than "trusted" \u2014 the harness left your setting; change it by hand` : "headless: agent-harness install \u2026 --with-codex-trust";
7956
8383
  log(cont(`open Codex here \u2192 ${c.bold('"Trust and continue"')} ${c.dim(`(${headless})`)}`));
7957
8384
  }
7958
8385
  if (spec.cli.includes("codex")) {
@@ -7961,7 +8388,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7961
8388
  );
7962
8389
  }
7963
8390
  const scaffoldFiles = scaffoldFilesForCli(spec.cli).filter(
7964
- (f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join27(spec.projectDir, f))
8391
+ (f) => f === "CLAUDE.md" && report.rootClaudeMd?.created === true || hasFillPrompt(join30(spec.projectDir, f))
7965
8392
  );
7966
8393
  if (scaffoldFiles.length > 0) {
7967
8394
  const auditInstalled = buildManifestSpec(spec).selectedInternalSkills?.includes("audit-harness-fit");
@@ -7977,7 +8404,7 @@ function renderFinalSummary(log, spec, report, fromWizard) {
7977
8404
  }
7978
8405
  function hasFillPrompt(path) {
7979
8406
  try {
7980
- return readFileSync21(path, "utf8").includes("<!-- FILL:");
8407
+ return readFileSync24(path, "utf8").includes("<!-- FILL:");
7981
8408
  } catch {
7982
8409
  return false;
7983
8410
  }
@@ -7995,7 +8422,7 @@ function scaffoldFilesForCli(cli) {
7995
8422
  if (cli.includes("claude")) {
7996
8423
  files.push("CLAUDE.md");
7997
8424
  }
7998
- if (cli.includes("codex") || cli.includes("opencode")) {
8425
+ if (cli.includes("codex") || cli.includes("opencode") || cli.includes("antigravity")) {
7999
8426
  files.push("AGENTS.md");
8000
8427
  }
8001
8428
  return files;
@@ -8083,12 +8510,19 @@ function renderPhase1Rows(log, baseline, verbose = false) {
8083
8510
  for (const f of baseline.updateMode.restoredFiles ?? []) {
8084
8511
  log(assetRow("success", f.path, restoredFilePart(f)));
8085
8512
  }
8513
+ const agentsNews = agentsMdNewsRow(baseline.updateMode.agentsMd);
8514
+ if (agentsNews) log(agentsNews);
8515
+ const retired = anchorRetireRow(baseline.updateMode.anchorRetired ?? null);
8516
+ if (retired) log(retired);
8086
8517
  for (const row of outOfTrackRows(baseline.updateMode.outOfTrack)) log(row);
8087
8518
  for (const f of baseline.updateMode.sharedWrites ?? []) {
8088
8519
  const row = sharedFileRow(f);
8089
8520
  if (row !== null) log(row);
8090
8521
  }
8091
- for (const f of baseline.updateMode.sharedLeft ?? []) log(sharedLeftRow(f));
8522
+ for (const f of baseline.updateMode.sharedLeft ?? []) {
8523
+ log(sharedLeftRow(f));
8524
+ if (f.retiredFeatures !== void 0) log(retiredFeaturesRow(f.path, f.retiredFeatures));
8525
+ }
8092
8526
  for (const row of excludedStillThereRows(baseline.updateMode.excludedStillThere ?? []))
8093
8527
  log(row);
8094
8528
  for (const r of baseline.updateMode.excludedKeys ?? []) {
@@ -8913,10 +9347,10 @@ function executeSpec(requested, deps = {}) {
8913
9347
  return;
8914
9348
  }
8915
9349
  if (deps.mode === "update" && report.backup !== null) {
8916
- const liveClaude = join28(spec.projectDir, ".claude");
9350
+ const liveClaude = join31(spec.projectDir, ".claude");
8917
9351
  if (dirTreesIdentical(liveClaude, report.backup)) {
8918
9352
  try {
8919
- rmSync3(report.backup, { recursive: true, force: true });
9353
+ rmSync4(report.backup, { recursive: true, force: true });
8920
9354
  report.backup = null;
8921
9355
  } catch {
8922
9356
  }
@@ -8940,8 +9374,8 @@ function resolveScopeOption(value, err) {
8940
9374
  }
8941
9375
  function unknownKeyWarning(id, flag, keys) {
8942
9376
  const prefix = id.slice(0, id.indexOf(":") + 1);
8943
- const same = [...keys].filter((k2) => k2.startsWith(prefix)).sort();
8944
- const shown = same.length > 0 ? same : [...keys].sort();
9377
+ const same2 = [...keys].filter((k2) => k2.startsWith(prefix)).sort();
9378
+ const shown = same2.length > 0 ? same2 : [...keys].sort();
8945
9379
  return shown.length > 0 ? `[WARN] Unknown key id '${id}' (${flag}). Skipping. Key ids this install writes: ${shown.join(", ")}` : `[WARN] Unknown key id '${id}' (${flag}). Skipping. This install writes no harness part with that name`;
8946
9380
  }
8947
9381
  function conflictingIds(options) {
@@ -9000,8 +9434,8 @@ function registerInstallCommand(cli) {
9000
9434
 
9001
9435
  // src/commands/list.ts
9002
9436
  init_esm_shims();
9003
- import { existsSync as existsSync24, readFileSync as readFileSync22 } from "fs";
9004
- import { join as join29, resolve as resolve3 } from "path";
9437
+ import { existsSync as existsSync24, readFileSync as readFileSync25 } from "fs";
9438
+ import { join as join32, resolve as resolve3 } from "path";
9005
9439
  function listAction(options = {}, deps = {}) {
9006
9440
  const log = deps.log ?? console.log;
9007
9441
  const err = deps.err ?? console.error;
@@ -9104,8 +9538,8 @@ function formatHarnessFileRows(log, projectDir) {
9104
9538
  const rows = [` ${c.dim(tops.length > 0 ? tops.join(" ") : "(none recorded)")}`];
9105
9539
  const rootMd = log.templates.rootClaudeMd;
9106
9540
  if (rootMd) {
9107
- const path = join29(projectDir, rootMd.path);
9108
- const modified = existsSync24(path) && hashContent(readFileSync22(path, "utf8")) !== rootMd.sha256;
9541
+ const path = join32(projectDir, rootMd.path);
9542
+ const modified = existsSync24(path) && hashContent(readFileSync25(path, "utf8")) !== rootMd.sha256;
9109
9543
  rows.push(
9110
9544
  modified ? ` ${c.dim(rootMd.path)} ${c.yellow("(\uC218\uC815\uB428 \u2014 uninstall \uC2DC \uBCF4\uC874)")}` : ` ${c.dim(rootMd.path)}`
9111
9545
  );
@@ -9119,7 +9553,7 @@ var ROOT_FILE_CHANGE = {
9119
9553
  displaced: "\uBE44\uCF1C \uB460"
9120
9554
  };
9121
9555
  function formatRootFileRows(rootFiles, projectDir) {
9122
- const present2 = rootFiles.filter((f) => existsSync24(join29(projectDir, f.path)));
9556
+ const present2 = rootFiles.filter((f) => existsSync24(join32(projectDir, f.path)));
9123
9557
  const width = Math.max(0, ...present2.map((f) => f.path.length));
9124
9558
  return present2.map(
9125
9559
  (f) => ` ${c.dim(padDisplay(f.path, width))} ${c.dim(ROOT_FILE_CHANGE[f.change])} ${c.dim(f.notes.join(" / "))}`
@@ -9140,16 +9574,16 @@ import {
9140
9574
  accessSync,
9141
9575
  existsSync as existsSync25,
9142
9576
  constants as fsConstants,
9143
- lstatSync as lstatSync4,
9144
- readdirSync as readdirSync5,
9145
- readFileSync as readFileSync23,
9146
- realpathSync as realpathSync2,
9577
+ lstatSync as lstatSync5,
9578
+ readdirSync as readdirSync6,
9579
+ readFileSync as readFileSync26,
9580
+ realpathSync as realpathSync3,
9147
9581
  rmdirSync as rmdirSync2,
9148
- rmSync as rmSync4,
9582
+ rmSync as rmSync5,
9149
9583
  statSync as statSync4,
9150
9584
  writeFileSync as writeFileSync9
9151
9585
  } from "fs";
9152
- import { dirname as dirname6, join as join30, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
9586
+ import { dirname as dirname6, join as join33, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
9153
9587
 
9154
9588
  // src/uninstall-interactive.ts
9155
9589
  init_esm_shims();
@@ -10765,7 +11199,7 @@ Proceed?`, initialValue: false });
10765
11199
 
10766
11200
  // src/commands/uninstall.ts
10767
11201
  function templateDirPath(projectDir, rel) {
10768
- return join30(projectDir, rel.replace(/\/+$/, ""));
11202
+ return join33(projectDir, rel.replace(/\/+$/, ""));
10769
11203
  }
10770
11204
  function outsideTemplateLink(projectDir, rel) {
10771
11205
  return outsideLinkAt(projectDir, rel.replace(/\/+$/, ""));
@@ -10988,7 +11422,7 @@ function unwritablePaths(ctx) {
10988
11422
  const project = ctx.targetAssets.filter((a) => a.scope === "project");
10989
11423
  if (project.some((a) => a.method === "npm")) need.add(".");
10990
11424
  const parentOf = (path) => {
10991
- const abs = join30(projectDir, path);
11425
+ const abs = join33(projectDir, path);
10992
11426
  if (!safeExists(abs)) return;
10993
11427
  const real = safeRealpath(abs);
10994
11428
  if (real !== null && isOutsideProject(projectDir, real)) return;
@@ -10999,7 +11433,7 @@ function unwritablePaths(ctx) {
10999
11433
  if (!ctx.keepTemplates) {
11000
11434
  const recordedShared = new Set((installLog.portions ?? []).map((p2) => p2.path));
11001
11435
  for (const p2 of ctx.sharedPaths) {
11002
- if (recordedShared.has(p2) && existsSync25(join30(projectDir, p2))) need.add(p2);
11436
+ if (recordedShared.has(p2) && existsSync25(join33(projectDir, p2))) need.add(p2);
11003
11437
  }
11004
11438
  for (const f of installLog.externalFiles ?? []) {
11005
11439
  if (!underAny(f.path, ctx.movedDirs)) parentOf(f.path);
@@ -11008,7 +11442,7 @@ function unwritablePaths(ctx) {
11008
11442
  }
11009
11443
  return [...need].filter((p2) => {
11010
11444
  try {
11011
- accessSync(join30(projectDir, p2), fsConstants.W_OK);
11445
+ accessSync(join33(projectDir, p2), fsConstants.W_OK);
11012
11446
  return false;
11013
11447
  } catch (e2) {
11014
11448
  return e2.code !== "ENOENT";
@@ -11017,7 +11451,7 @@ function unwritablePaths(ctx) {
11017
11451
  }
11018
11452
  function safeExists(abs) {
11019
11453
  try {
11020
- lstatSync4(abs);
11454
+ lstatSync5(abs);
11021
11455
  return true;
11022
11456
  } catch {
11023
11457
  return false;
@@ -11092,7 +11526,7 @@ function settleLog(ctx, io) {
11092
11526
  return true;
11093
11527
  }
11094
11528
  } else {
11095
- io.rm(join30(projectDir, INSTALL_LOG_DIR));
11529
+ io.rm(join33(projectDir, INSTALL_LOG_DIR));
11096
11530
  if (outsideTemplateLink(projectDir, LEGACY_INSTALL_LOG_DIR) === null) {
11097
11531
  io.rm(legacyInstallLogPath(projectDir));
11098
11532
  }
@@ -11118,7 +11552,7 @@ function dryRunLines(plan, installLog, projectDir, keepTemplates, rootFiles, har
11118
11552
  for (const rel of dirs) {
11119
11553
  const kept = outsideTemplateLink(projectDir, rel);
11120
11554
  if (kept) lines2.push(keptOutsideLinkLine(kept, installLog));
11121
- else if (existsSync25(join30(projectDir, rel))) lines2.push(MOVE_ASIDE_PREVIEW(rel));
11555
+ else if (existsSync25(join33(projectDir, rel))) lines2.push(MOVE_ASIDE_PREVIEW(rel));
11122
11556
  }
11123
11557
  if (dirs.length === 0) lines2.push(" \u25CB remove templates: (none)");
11124
11558
  const rootMd = installLog.templates.rootClaudeMd;
@@ -11148,7 +11582,7 @@ function advisoryLines(plan, projectDir, rootFiles, keepTemplates = false, globa
11148
11582
  "",
11149
11583
  c.yellow("[GLOBAL] Codex trust entry \u2014 remove by hand (never deleted automatically):"),
11150
11584
  c.dim(` \xB7 ${globalTrust.configPath}`),
11151
- c.dim(` [projects."${globalTrust.projectDir}"]`)
11585
+ c.dim(` [projects.${tomlBasicString(globalTrust.projectDir)}]`)
11152
11586
  );
11153
11587
  }
11154
11588
  if (plan.globalAdvisories.length > 0) {
@@ -11169,14 +11603,14 @@ function advisoryLines(plan, projectDir, rootFiles, keepTemplates = false, globa
11169
11603
  function backupArtifactAdvisoryLines(projectDir) {
11170
11604
  const stampRe = /\.backup-\d{8}T\d{6}$/;
11171
11605
  const found = [];
11172
- for (const e2 of readdirSync5(projectDir, { withFileTypes: true })) {
11606
+ for (const e2 of readdirSync6(projectDir, { withFileTypes: true })) {
11173
11607
  if ([".claude", ".codex", ".opencode"].some((d2) => e2.name.startsWith(`${d2}.backup-`))) {
11174
11608
  found.push(e2.name);
11175
11609
  } else if (e2.isFile() && stampRe.test(e2.name)) {
11176
11610
  found.push(e2.name);
11177
11611
  }
11178
11612
  }
11179
- const agents = join30(projectDir, ".agents");
11613
+ const agents = join33(projectDir, ".agents");
11180
11614
  if (existsSync25(agents)) {
11181
11615
  for (const rel of listFilesRecursive(agents)) {
11182
11616
  if (stampRe.test(rel)) found.push(`.agents/${rel}`);
@@ -11192,11 +11626,11 @@ function backupArtifactAdvisoryLines(projectDir) {
11192
11626
  }
11193
11627
  function rootFileAdvisoryLines(rootFiles, projectDir, keepTemplates = false) {
11194
11628
  const header = keepTemplates ? "[LEFT] uninstall \uD6C4 \uB0A8\uB294 \uAC83 \u2014 templates \uBCF4\uC874(.claude/ \uB4F1) + \uB8E8\uD2B8 \uD30C\uC77C (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):" : "[ROOT] `.claude/` \uBC16\uC5D0 \uB0A8\uB294 \uAC83 (\uC790\uB3D9\uC73C\uB85C \uC9C0\uC6B0\uC9C0 \uC54A\uB294\uB2E4):";
11195
- const present2 = rootFiles.filter((f) => existsSync25(join30(projectDir, f.path)));
11629
+ const present2 = rootFiles.filter((f) => existsSync25(join33(projectDir, f.path)));
11196
11630
  if (present2.length === 0) return [];
11197
11631
  const outside = /* @__PURE__ */ new Map();
11198
11632
  const inside = present2.filter((f) => {
11199
- const o = outsideProjectTarget(projectDir, join30(projectDir, f.path));
11633
+ const o = outsideProjectTarget(projectDir, join33(projectDir, f.path));
11200
11634
  if (o === null) return true;
11201
11635
  const group = outside.get(o.link) ?? { link: o, files: [] };
11202
11636
  group.files.push(f.path);
@@ -11235,7 +11669,7 @@ function rootFileMeaning(f, projectDir) {
11235
11669
  case "displaced": {
11236
11670
  const backup = f.notes[0];
11237
11671
  if (backup === void 0) return "\uD558\uB124\uC2A4 \uD310\uACFC \uAC19\uC740 \uC124\uCE58\uC790 \uD30C\uC77C\uC774 \uC788\uB358 \uC790\uB9AC (\uADF8\uB300\uB85C \uB450\uC5C8\uB2E4)";
11238
- return existsSync25(join30(projectDir, backup)) ? `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC740 ${backup} \uC5D0 \uC788\uB2E4` : `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC758 \uBC31\uC5C5(${backup})\uC774 \uB354\uB294 \uC5C6\uB2E4`;
11672
+ return existsSync25(join33(projectDir, backup)) ? `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC740 ${backup} \uC5D0 \uC788\uB2E4` : `\uD558\uB124\uC2A4\uAC00 \uC4F4 \uC790\uB9AC \u2014 \uC6D0\uB798 \uC788\uB358 \uC124\uCE58\uC790 \uD30C\uC77C\uC758 \uBC31\uC5C5(${backup})\uC774 \uB354\uB294 \uC5C6\uB2E4`;
11239
11673
  }
11240
11674
  }
11241
11675
  }
@@ -11289,6 +11723,7 @@ function planCliRemoval(target, remaining, installLog) {
11289
11723
  (f) => underAny(f.path, recorded2)
11290
11724
  );
11291
11725
  return {
11726
+ target,
11292
11727
  dirs: owned.filter((o) => o.kind === "dir").map((o) => o.path),
11293
11728
  shared: CLI_SHARED.filter((p2) => recorded2.includes(p2)),
11294
11729
  scoped: { ...installLog, externalFiles: scopedFiles },
@@ -11313,7 +11748,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
11313
11748
  for (const dir of plan.dirs) {
11314
11749
  const kept = outsideTemplateLink(projectDir, dir);
11315
11750
  if (kept) lines2.push(keptOutsideLinkLine(kept, installLog));
11316
- else if (existsSync25(join30(projectDir, dir))) lines2.push(MOVE_ASIDE_PREVIEW(dir));
11751
+ else if (existsSync25(join33(projectDir, dir))) lines2.push(MOVE_ASIDE_PREVIEW(dir));
11317
11752
  }
11318
11753
  lines2.push(...previewExternalLines(plan.scoped, projectDir, harnessRoot));
11319
11754
  lines2.push(
@@ -11323,7 +11758,7 @@ function removeCliDryRunLines(plan, ctx, remaining) {
11323
11758
  )
11324
11759
  );
11325
11760
  const rootMd = plan.anchor ? installLog.templates.rootClaudeMd : void 0;
11326
- if (rootMd && existsSync25(join30(projectDir, rootMd.path))) {
11761
+ if (rootMd && existsSync25(join33(projectDir, rootMd.path))) {
11327
11762
  lines2.push(
11328
11763
  rootClaudeMdModified(installLog, projectDir) ? ` \u25CB keep ${rootMd.path} (modified since install \u2014 preserved)` : ` \u25CB remove ${rootMd.path}`
11329
11764
  );
@@ -11331,20 +11766,73 @@ function removeCliDryRunLines(plan, ctx, remaining) {
11331
11766
  if (plan.importBlock && hasRootImport(projectDir)) {
11332
11767
  lines2.push(" \u25CB strip harness @import from CLAUDE.md (\uBCF8\uBB38 \uBCF4\uC874)");
11333
11768
  }
11769
+ const rerender = agentsMdRerenderSet(plan.target, remaining, installLog, projectDir);
11770
+ if (rerender !== null) {
11771
+ lines2.push(` \u25CB would re-render AGENTS.md for ${displayNames(rerender)} (your sections kept)`);
11772
+ }
11334
11773
  lines2.push(` \u25CB install log updated (clis: ${remaining.join(", ")})`, "");
11335
11774
  return lines2;
11336
11775
  }
11776
+ function displayNames(clis) {
11777
+ return clis.map((c3) => CLI_DISPLAY_NAMES[c3]).join(" \xB7 ");
11778
+ }
11779
+ function agentsMdRerenderSet(target, remaining, log, projectDir) {
11780
+ if (!isAgentsMdCli(target)) return null;
11781
+ const set = agentsMdClis(remaining);
11782
+ if (set.length === 0) return null;
11783
+ const recorded2 = (log.externalFiles ?? []).some((f) => f.path === AGENTS_MD2) || (log.portions ?? []).some((p2) => p2.path === AGENTS_MD2);
11784
+ if (!recorded2 || !existsSync25(join33(projectDir, AGENTS_MD2))) return null;
11785
+ return set;
11786
+ }
11787
+ function rerenderAgentsMd(ctx, set) {
11788
+ const { installLog, projectDir, harnessRoot } = ctx;
11789
+ return runAgentsMdTransform({
11790
+ harnessRoot,
11791
+ projectDir,
11792
+ clis: agentsMdClis(set),
11793
+ ...recordedRenderInputs(projectDir, installLog),
11794
+ baseline: new Map((installLog.externalFiles ?? []).map((f) => [f.path, f.sha256])),
11795
+ refreshOnly: true,
11796
+ installedByRecord: true,
11797
+ shared: { portions: installLog.portions ?? [], excluded: [...excludedIds(installLog)] }
11798
+ });
11799
+ }
11800
+ function withRerender(next, r) {
11801
+ const out = { ...next };
11802
+ const files = mergeExternalFiles(next.externalFiles, r.ownership.files);
11803
+ if (files.length > 0) out.externalFiles = files;
11804
+ const block = r.agentsMd.shared;
11805
+ if (block !== null && block.portions !== null) {
11806
+ const portions = [
11807
+ ...(next.portions ?? []).filter((p2) => p2.path !== AGENTS_MD2),
11808
+ ...block.portions
11809
+ ];
11810
+ if (portions.length > 0) out.portions = portions;
11811
+ else delete out.portions;
11812
+ }
11813
+ return out;
11814
+ }
11815
+ function rerenderLines(r) {
11816
+ const names = displayNames(r.clis);
11817
+ const lines2 = r.agentsMd.model === "block" ? [` ${status.success(`AGENTS.md \u2014 harness block re-rendered for ${names} (yours stays)`)}`] : [` ${status.success(`AGENTS.md \u2014 re-rendered for ${names} (your sections kept)`)}`];
11818
+ for (const b2 of r.ownership.backedUp) {
11819
+ lines2.push(
11820
+ ` ${c.yellow("\u2298")} ${b2} \u2014 changed since the harness last wrote it; saved a copy as ${b2}.backup-<time> \xB7 your sections are kept`
11821
+ );
11822
+ }
11823
+ return lines2;
11824
+ }
11337
11825
  function removeHarnessAnchor(ctx, io) {
11338
11826
  const { installLog, projectDir } = ctx;
11339
11827
  const rootMd = installLog.templates.rootClaudeMd;
11340
- if (!rootMd || !existsSync25(join30(projectDir, rootMd.path))) return false;
11828
+ if (!rootMd || !existsSync25(join33(projectDir, rootMd.path))) return false;
11341
11829
  if (rootClaudeMdModified(installLog, projectDir)) {
11342
11830
  io.log(
11343
11831
  ` ${c.yellow("\u2298")} ${rootMd.path} kept \u2014 modified since install. Remove manually if intended.`
11344
11832
  );
11345
11833
  return false;
11346
11834
  }
11347
- io.rm(join30(projectDir, rootMd.path));
11835
+ io.rm(join33(projectDir, rootMd.path));
11348
11836
  io.log(` ${status.success(`${rootMd.path} removed`)}`);
11349
11837
  return true;
11350
11838
  }
@@ -11402,7 +11890,7 @@ function removeCliAction(ctx, io) {
11402
11890
  io.log(keptOutsideLinkLine(kept, installLog));
11403
11891
  continue;
11404
11892
  }
11405
- if (!existsSync25(join30(projectDir, dir))) continue;
11893
+ if (!existsSync25(join33(projectDir, dir))) continue;
11406
11894
  const backup = io.moveAside(templateDirPath(projectDir, dir));
11407
11895
  if (backup) io.log(movedAsideLine(projectDir, dir, backup));
11408
11896
  }
@@ -11414,8 +11902,12 @@ function removeCliAction(ctx, io) {
11414
11902
  io.log(` ${status.success("CLAUDE.md \u2014 harness @import removed (\uBCF8\uBB38 \uBCF4\uC874)")}`);
11415
11903
  }
11416
11904
  const anchorRemoved = plan.anchor ? removeHarnessAnchor(ctx, io) : false;
11905
+ const rerenderSet = agentsMdRerenderSet(target, remaining, installLog, projectDir);
11906
+ const rerender = rerenderSet === null ? null : rerenderAgentsMd(ctx, rerenderSet);
11907
+ if (rerender !== null) for (const line of rerenderLines(rerender)) io.log(line);
11417
11908
  const recovered = /* @__PURE__ */ new Set([...external.removed, ...external.stripped]);
11418
- const next = settleCliLog(ctx, target, remaining, recovered, anchorRemoved, plan.dirs, shared2);
11909
+ const settled = settleCliLog(ctx, target, remaining, recovered, anchorRemoved, plan.dirs, shared2);
11910
+ const next = rerender === null ? settled : withRerender(settled, rerender);
11419
11911
  try {
11420
11912
  io.writeLog(projectDir, next);
11421
11913
  io.log(` ${status.success(`install log updated (clis: ${remaining.join(", ")})`)}`);
@@ -11439,7 +11931,7 @@ var SKILLS_LOCK = "skills-lock.json";
11439
11931
  function withSkillsLock(rootFiles, assets, projectDir) {
11440
11932
  const out = [...rootFiles];
11441
11933
  const skills = assets.some((a) => a.method === "skill" && a.scope === "project");
11442
- if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync25(join30(projectDir, SKILLS_LOCK))) {
11934
+ if (skills && !out.some((f) => f.path === SKILLS_LOCK) && existsSync25(join33(projectDir, SKILLS_LOCK))) {
11443
11935
  out.push({ path: SKILLS_LOCK, change: "advisory", notes: [] });
11444
11936
  }
11445
11937
  return out;
@@ -11463,7 +11955,7 @@ function remnantFor(path, log, harnessRoot) {
11463
11955
  };
11464
11956
  }
11465
11957
  if (path === AGENTS_MD2) {
11466
- if (!clis.includes("codex") && !clis.includes("opencode")) return () => [];
11958
+ if (!clis.some(isAgentsMdCli)) return () => [];
11467
11959
  return (disk) => ADAPTERS["marker-md"].read(disk, [AGENTS_BLOCK_NAME])?.has(AGENTS_BLOCK_NAME) ? [AGENTS_BLOCK_NAME] : [];
11468
11960
  }
11469
11961
  if (!clis.includes("opencode")) return () => [];
@@ -11563,7 +12055,7 @@ function noReverseHints(asset, ctx) {
11563
12055
  }
11564
12056
  if (asset.method !== "skill") return [];
11565
12057
  const name = asset.detail.skill;
11566
- const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => !underAny(p2, ctx.movedDirs) && existsSync25(join30(ctx.projectDir, p2)));
12058
+ const left = name === void 0 ? [] : [".claude/skills", ".agents/skills"].map((root) => `${root}/${name}/`).filter((p2) => !underAny(p2, ctx.movedDirs) && existsSync25(join33(ctx.projectDir, p2)));
11567
12059
  return [
11568
12060
  left.length > 0 ? `files it put down are not on record (installed by an older version) \u2014 left: ${left.join(" \xB7 ")} (delete by hand if the harness installed it)` : "files it put down are not on record (installed by an older version) \u2014 nothing removed"
11569
12061
  ];
@@ -11670,15 +12162,14 @@ function removeTemplates(log, projectDir, io, harnessRoot) {
11670
12162
  if (rootMd) {
11671
12163
  if (rootClaudeMdModified(log, projectDir))
11672
12164
  return { rootClaudeMdKept: true, importStripped, external, moved, keptLinks };
11673
- rm(join30(projectDir, rootMd.path));
12165
+ rm(join33(projectDir, rootMd.path));
11674
12166
  }
11675
12167
  return { rootClaudeMdKept: false, importStripped, external, moved, keptLinks };
11676
12168
  }
11677
12169
  var AGENTS_MD2 = "AGENTS.md";
11678
- function readAgentsMdTemplate(harnessRoot, log) {
11679
- const flavor = log.templates.opencodeDir ? "opencode" : "codex";
12170
+ function readAgentsMdTemplate(harnessRoot) {
11680
12171
  try {
11681
- return readFileSync23(join30(harnessRoot, "templates", flavor, "AGENTS.md.template"), "utf8");
12172
+ return readFileSync26(join33(harnessRoot, AGENTS_MD_TEMPLATE), "utf8");
11682
12173
  } catch {
11683
12174
  return null;
11684
12175
  }
@@ -11689,7 +12180,7 @@ function recoverMovedSymlinkPortions(log, projectDir, dirs) {
11689
12180
  for (const path of paths) {
11690
12181
  if (path.startsWith(".agents/skills/")) continue;
11691
12182
  if (!dirs.some((d2) => path.startsWith(d2))) continue;
11692
- const abs = join30(projectDir, path);
12183
+ const abs = join33(projectDir, path);
11693
12184
  if (!safeIsSymlink(abs)) continue;
11694
12185
  const target = safeRealpath(abs);
11695
12186
  if (target !== null && isOutsideProject(projectDir, target)) {
@@ -11718,11 +12209,11 @@ function recoverMovedSymlinkTargets(log, projectDir, dirs, rm) {
11718
12209
  for (const { path, sha256 } of log.externalFiles ?? []) {
11719
12210
  if (path.startsWith(".agents/skills/")) continue;
11720
12211
  if (!dirs.some((d2) => path.startsWith(d2))) continue;
11721
- const abs = join30(projectDir, path);
12212
+ const abs = join33(projectDir, path);
11722
12213
  if (!safeIsSymlink(abs)) continue;
11723
12214
  let target;
11724
12215
  try {
11725
- target = realpathSync2(abs);
12216
+ target = realpathSync3(abs);
11726
12217
  if (!statSync4(target).isFile()) continue;
11727
12218
  } catch {
11728
12219
  continue;
@@ -11732,7 +12223,7 @@ function recoverMovedSymlinkTargets(log, projectDir, dirs, rm) {
11732
12223
  continue;
11733
12224
  }
11734
12225
  try {
11735
- if (hashContent(readFileSync23(target, "utf8")) !== sha256) {
12226
+ if (hashContent(readFileSync26(target, "utf8")) !== sha256) {
11736
12227
  kept.push(path);
11737
12228
  continue;
11738
12229
  }
@@ -11746,14 +12237,14 @@ function recoverMovedSymlinkTargets(log, projectDir, dirs, rm) {
11746
12237
  }
11747
12238
  function safeRealpath(abs) {
11748
12239
  try {
11749
- return realpathSync2(abs);
12240
+ return realpathSync3(abs);
11750
12241
  } catch {
11751
12242
  return null;
11752
12243
  }
11753
12244
  }
11754
12245
  function safeIsSymlink(abs) {
11755
12246
  try {
11756
- return lstatSync4(abs).isSymbolicLink();
12247
+ return lstatSync5(abs).isSymbolicLink();
11757
12248
  } catch {
11758
12249
  return false;
11759
12250
  }
@@ -11761,10 +12252,17 @@ function safeIsSymlink(abs) {
11761
12252
  function neutralizeScaffoldBanner(text) {
11762
12253
  return text.split("\n").filter((line) => !line.includes("SCAFFOLD \u2014 not filled in yet")).join("\n");
11763
12254
  }
11764
- function agentsMdRemainder(current, harnessRoot, log) {
11765
- const template = readAgentsMdTemplate(harnessRoot, log);
12255
+ function agentsMdRemainder(current, harnessRoot) {
12256
+ const template = readAgentsMdTemplate(harnessRoot);
11766
12257
  if (template === null) return { ok: false };
11767
- return { ok: true, remainder: stripHarnessFromAgentsMd({ existing: current, template }) };
12258
+ return {
12259
+ ok: true,
12260
+ remainder: stripHarnessFromAgentsMd({
12261
+ existing: current,
12262
+ template,
12263
+ harnessRuleHeadings: ruleHeadings(harnessRoot)
12264
+ })
12265
+ };
11768
12266
  }
11769
12267
  function removeExternalFiles(log, projectDir, rm, harnessRoot) {
11770
12268
  const removed = [];
@@ -11773,22 +12271,22 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
11773
12271
  const unjudged = [];
11774
12272
  const outside = [];
11775
12273
  for (const { path, sha256 } of log.externalFiles ?? []) {
11776
- const abs = join30(projectDir, path);
12274
+ const abs = join33(projectDir, path);
11777
12275
  if (!existsSync25(abs)) continue;
11778
12276
  let current;
11779
12277
  let effective = abs;
11780
12278
  const foreignSkillSlot = path.startsWith(".agents/skills/");
11781
12279
  try {
11782
- const isLink = lstatSync4(abs).isSymbolicLink();
12280
+ const isLink = lstatSync5(abs).isSymbolicLink();
11783
12281
  if (isLink && foreignSkillSlot) continue;
11784
- const resolved = realpathSync2(abs);
12282
+ const resolved = realpathSync3(abs);
11785
12283
  if (!statSync4(resolved).isFile()) continue;
11786
12284
  if (isOutsideProject(projectDir, resolved)) {
11787
12285
  outside.push({ path, target: resolved });
11788
12286
  continue;
11789
12287
  }
11790
12288
  if (isLink) effective = resolved;
11791
- current = readFileSync23(effective, "utf8");
12289
+ current = readFileSync26(effective, "utf8");
11792
12290
  } catch {
11793
12291
  unjudged.push(path);
11794
12292
  continue;
@@ -11798,7 +12296,7 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
11798
12296
  continue;
11799
12297
  }
11800
12298
  if (path === AGENTS_MD2) {
11801
- const verdict = agentsMdRemainder(current, harnessRoot, log);
12299
+ const verdict = agentsMdRemainder(current, harnessRoot);
11802
12300
  if (!verdict.ok) {
11803
12301
  unjudged.push(path);
11804
12302
  continue;
@@ -11822,11 +12320,11 @@ function removeExternalFiles(log, projectDir, rm, harnessRoot) {
11822
12320
  }
11823
12321
  removed.push(path);
11824
12322
  }
11825
- for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join30(projectDir, path)));
11826
- const decisions = join30(projectDir, "docs", "decisions");
12323
+ for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join33(projectDir, path)));
12324
+ const decisions = join33(projectDir, "docs", "decisions");
11827
12325
  try {
11828
- if (existsSync25(decisions) && readdirSync5(decisions).length === 0)
11829
- rmSync4(decisions, { recursive: true });
12326
+ if (existsSync25(decisions) && readdirSync6(decisions).length === 0)
12327
+ rmSync5(decisions, { recursive: true });
11830
12328
  } catch {
11831
12329
  }
11832
12330
  return { removed, kept, stripped, unjudged, outside };
@@ -11836,10 +12334,10 @@ function removeToolFiles(files, projectDir, rm) {
11836
12334
  const removed = [];
11837
12335
  let failed = 0;
11838
12336
  for (const { path, sha256 } of files) {
11839
- const abs = join30(projectDir, path);
12337
+ const abs = join33(projectDir, path);
11840
12338
  let isLink;
11841
12339
  try {
11842
- isLink = lstatSync4(abs).isSymbolicLink();
12340
+ isLink = lstatSync5(abs).isSymbolicLink();
11843
12341
  } catch {
11844
12342
  continue;
11845
12343
  }
@@ -11853,7 +12351,7 @@ function removeToolFiles(files, projectDir, rm) {
11853
12351
  continue;
11854
12352
  }
11855
12353
  try {
11856
- if (hashContent(readFileSync23(abs, "utf8")) !== sha256) {
12354
+ if (hashContent(readFileSync26(abs, "utf8")) !== sha256) {
11857
12355
  const backup = backupFile(abs);
11858
12356
  notes.push(`backed up ${path} \u2192 ${relative5(projectDir, backup)} (changed since install)`);
11859
12357
  }
@@ -11864,7 +12362,7 @@ function removeToolFiles(files, projectDir, rm) {
11864
12362
  notes.push(`left ${path} \u2014 ${e2 instanceof Error ? e2.message : String(e2)}`);
11865
12363
  }
11866
12364
  }
11867
- for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join30(projectDir, path)));
12365
+ for (const path of removed) pruneEmptyDirsUpward(projectDir, dirname6(join33(projectDir, path)));
11868
12366
  return failed > 0 ? { ok: false, message: `${failed} file(s) could not be removed`, notes } : { ok: true, notes };
11869
12367
  }
11870
12368
  function pruneEmptyDirsUpward(projectDir, startDir) {
@@ -11873,7 +12371,7 @@ function pruneEmptyDirsUpward(projectDir, startDir) {
11873
12371
  const rel = relative5(projectDir, cursor);
11874
12372
  if (rel === "" || rel.startsWith(`..${sep5}`) || rel === "..") return;
11875
12373
  try {
11876
- if (readdirSync5(cursor).length > 0) return;
12374
+ if (readdirSync6(cursor).length > 0) return;
11877
12375
  rmdirSync2(cursor);
11878
12376
  } catch {
11879
12377
  return;
@@ -11886,8 +12384,8 @@ function stripRootImport(projectDir) {
11886
12384
  const stripped = current === null ? null : stripHarnessImport(current);
11887
12385
  if (stripped === null) return false;
11888
12386
  try {
11889
- backupIfLossyUtf8(join30(projectDir, "CLAUDE.md"));
11890
- writeFileSync9(join30(projectDir, "CLAUDE.md"), stripped, "utf8");
12387
+ backupIfLossyUtf8(join33(projectDir, "CLAUDE.md"));
12388
+ writeFileSync9(join33(projectDir, "CLAUDE.md"), stripped, "utf8");
11891
12389
  return true;
11892
12390
  } catch {
11893
12391
  return false;
@@ -11897,7 +12395,7 @@ function previewExternalLines(installLog, projectDir, harnessRoot, movedDirs = [
11897
12395
  const lines2 = [];
11898
12396
  let removable = 0;
11899
12397
  for (const { path, sha256 } of installLog.externalFiles ?? []) {
11900
- const abs = join30(projectDir, path);
12398
+ const abs = join33(projectDir, path);
11901
12399
  const underMoved = underAny(path, movedDirs);
11902
12400
  const isLink = !underMoved || existsSync25(abs) ? safeIsSymlink(abs) : false;
11903
12401
  if (underMoved && !isLink) continue;
@@ -11905,9 +12403,9 @@ function previewExternalLines(installLog, projectDir, harnessRoot, movedDirs = [
11905
12403
  let effective = abs;
11906
12404
  const foreignSkillSlot = path.startsWith(".agents/skills/");
11907
12405
  try {
11908
- const isLinkPath = lstatSync4(abs).isSymbolicLink();
12406
+ const isLinkPath = lstatSync5(abs).isSymbolicLink();
11909
12407
  if (isLinkPath && foreignSkillSlot) continue;
11910
- const resolved = realpathSync2(abs);
12408
+ const resolved = realpathSync3(abs);
11911
12409
  if (!statSync4(resolved).isFile()) continue;
11912
12410
  if (isOutsideProject(projectDir, resolved)) {
11913
12411
  lines2.push(` \u25CB keep ${path} (link target outside project: ${resolved} \u2014 preserved)`);
@@ -11917,13 +12415,13 @@ function previewExternalLines(installLog, projectDir, harnessRoot, movedDirs = [
11917
12415
  } catch {
11918
12416
  continue;
11919
12417
  }
11920
- const current = readFileSync23(effective, "utf8");
12418
+ const current = readFileSync26(effective, "utf8");
11921
12419
  if (hashContent(current) !== sha256) {
11922
12420
  lines2.push(` \u25CB keep ${path} (modified since install \u2014 preserved)`);
11923
12421
  continue;
11924
12422
  }
11925
12423
  if (path === AGENTS_MD2) {
11926
- const verdict = agentsMdRemainder(current, harnessRoot, installLog);
12424
+ const verdict = agentsMdRemainder(current, harnessRoot);
11927
12425
  if (!verdict.ok) {
11928
12426
  lines2.push(` \u25CB keep ${path} (harness sections could not be separated \u2014 preserved)`);
11929
12427
  continue;
@@ -11981,10 +12479,10 @@ function hasRootImport(projectDir) {
11981
12479
  return current !== null && stripHarnessImport(current) !== null;
11982
12480
  }
11983
12481
  function readRootClaudeMd(projectDir) {
11984
- const path = join30(projectDir, "CLAUDE.md");
12482
+ const path = join33(projectDir, "CLAUDE.md");
11985
12483
  if (!existsSync25(path)) return null;
11986
12484
  try {
11987
- return readFileSync23(path, "utf8");
12485
+ return readFileSync26(path, "utf8");
11988
12486
  } catch {
11989
12487
  return null;
11990
12488
  }
@@ -11992,16 +12490,16 @@ function readRootClaudeMd(projectDir) {
11992
12490
  function rootClaudeMdModified(log, projectDir) {
11993
12491
  const rootMd = log.templates.rootClaudeMd;
11994
12492
  if (!rootMd) return false;
11995
- const path = join30(projectDir, rootMd.path);
12493
+ const path = join33(projectDir, rootMd.path);
11996
12494
  if (!existsSync25(path)) return false;
11997
- return hashContent(readFileSync23(path, "utf8")) !== rootMd.sha256;
12495
+ return hashContent(readFileSync26(path, "utf8")) !== rootMd.sha256;
11998
12496
  }
11999
12497
  function defaultSpawn2(cmd, args) {
12000
12498
  return spawnSync2(cmd, [...args], { encoding: "utf8", stdio: "pipe", timeout: 12e4 });
12001
12499
  }
12002
12500
  function defaultRm(path) {
12003
12501
  if (existsSync25(path)) {
12004
- rmSync4(path, { recursive: true, force: true });
12502
+ rmSync5(path, { recursive: true, force: true });
12005
12503
  }
12006
12504
  }
12007
12505
  function registerUninstallCommand(cli) {