oh-my-customcode 1.1.106 → 2.0.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
@@ -430,18 +430,10 @@ var MESSAGES = {
430
430
  "codex.installing_npm": "Installing Codex CLI via npm...",
431
431
  "codex.install_failed": "Failed to install Codex CLI: {{error}}",
432
432
  "codex.unsupported_os": "Codex CLI auto-install is not supported on {{os}}",
433
- "rtk.already_installed": "RTK is already installed",
434
- "rtk.installing_brew": "Installing RTK via Homebrew...",
435
- "rtk.installing_curl": "Installing RTK via install script...",
436
- "rtk.install_failed": "Failed to install RTK: {{error}}",
437
- "rtk.unsupported_os": "RTK auto-install is not supported on {{os}}",
438
433
  "mcp.ontology_rag_configured": "ontology-rag MCP server configured successfully",
439
434
  "install.codex_installing": "Installing Codex CLI...",
440
435
  "install.codex_success": "Codex CLI installed successfully",
441
436
  "install.codex_already": "Codex CLI is already installed",
442
- "install.rtk_installing": "Installing RTK...",
443
- "install.rtk_success": "RTK installed successfully",
444
- "install.rtk_already": "RTK is already installed",
445
437
  "install.preserved": "Preserved {{files}} file(s) and {{dirs}} directory(ies) from the existing installation",
446
438
  "install.restored": "Restored {{files}} file(s) and {{dirs}} directory(ies) after installation",
447
439
  "install.backup_failed": "Failed to back up {{path}}: {{error}}",
@@ -453,7 +445,6 @@ var MESSAGES = {
453
445
  "preserve.restore_failed": "Failed to restore file {{file}}: {{error}}",
454
446
  "preserve_files.invalid_path": "Ignored invalid preserve_files entry from {{source}}: {{path}} ({{reason}})",
455
447
  "update.codex_missing": "Codex CLI is not installed",
456
- "update.rtk_missing": "RTK is not installed",
457
448
  "update.self_update_skipped": "Skipped update: source project cannot update itself",
458
449
  "update.deprecated_file_invalid_path": "Skipped deprecated file with invalid path: {{path}} ({{reason}})",
459
450
  "update.deprecated_file_removed": "Removed deprecated file: {{path}} ({{reason}})",
@@ -552,18 +543,10 @@ var MESSAGES = {
552
543
  "codex.installing_npm": "npm으로 Codex CLI를 설치합니다...",
553
544
  "codex.install_failed": "Codex CLI 설치에 실패했습니다: {{error}}",
554
545
  "codex.unsupported_os": "{{os}}에서는 Codex CLI 자동 설치를 지원하지 않습니다",
555
- "rtk.already_installed": "RTK가 이미 설치되어 있습니다",
556
- "rtk.installing_brew": "Homebrew로 RTK를 설치합니다...",
557
- "rtk.installing_curl": "설치 스크립트로 RTK를 설치합니다...",
558
- "rtk.install_failed": "RTK 설치에 실패했습니다: {{error}}",
559
- "rtk.unsupported_os": "{{os}}에서는 RTK 자동 설치를 지원하지 않습니다",
560
546
  "mcp.ontology_rag_configured": "ontology-rag MCP 서버 구성을 완료했습니다",
561
547
  "install.codex_installing": "Codex CLI를 설치합니다...",
562
548
  "install.codex_success": "Codex CLI를 설치했습니다",
563
549
  "install.codex_already": "Codex CLI가 이미 설치되어 있습니다",
564
- "install.rtk_installing": "RTK를 설치합니다...",
565
- "install.rtk_success": "RTK를 설치했습니다",
566
- "install.rtk_already": "RTK가 이미 설치되어 있습니다",
567
550
  "install.preserved": "기존 설치에서 파일 {{files}}개와 디렉토리 {{dirs}}개를 보존했습니다",
568
551
  "install.restored": "설치 후 파일 {{files}}개와 디렉토리 {{dirs}}개를 복원했습니다",
569
552
  "install.backup_failed": "{{path}} 백업에 실패했습니다: {{error}}",
@@ -575,7 +558,6 @@ var MESSAGES = {
575
558
  "preserve.restore_failed": "파일 {{file}} 복원에 실패했습니다: {{error}}",
576
559
  "preserve_files.invalid_path": "{{source}}의 유효하지 않은 preserve_files 항목을 무시했습니다: {{path}} ({{reason}})",
577
560
  "update.codex_missing": "Codex CLI가 설치되어 있지 않습니다",
578
- "update.rtk_missing": "RTK가 설치되어 있지 않습니다",
579
561
  "update.self_update_skipped": "업데이트를 건너뜁니다: 소스 프로젝트는 스스로 업데이트할 수 없습니다",
580
562
  "update.deprecated_file_invalid_path": "경로가 유효하지 않아 지원 중단 파일을 건너뜁니다: {{path}} ({{reason}})",
581
563
  "update.deprecated_file_removed": "지원 중단 파일을 삭제했습니다: {{path}} ({{reason}})",
@@ -1173,6 +1155,7 @@ var LEGACY_OMCUSTOM_DESCRIPTIONS = [
1173
1155
  ];
1174
1156
  var STANDALONE_SCRIPT_COMMAND = /^(?:bash )?(?:"(?:\$\{CLAUDE_PROJECT_DIR:-\.\}\/|\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)"|(?:\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh))$/;
1175
1157
  var SCRIPT_REFERENCE = /\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)/g;
1158
+ var RETIRED_RTK_DESCRIPTION = "RTK auto-intercept — transparently rewrites CLI commands through RTK proxy when available (R013 advisory)";
1176
1159
  function matcherKey(group) {
1177
1160
  return typeof group.matcher === "string" ? group.matcher : "";
1178
1161
  }
@@ -1234,15 +1217,52 @@ function isOwnedCommand(hook, owned) {
1234
1217
  const script = match?.[1] ?? match?.[2];
1235
1218
  return script !== undefined && owned.scripts.has(script);
1236
1219
  }
1237
- function userResidual(group, owned, ownership) {
1220
+ function isRetiredRtkCommand(hook, event, matcher) {
1221
+ if (event !== "PreToolUse" || matcher !== "Bash" || !isRecord(hook) || hook.type !== "command" || typeof hook.command !== "string") {
1222
+ return false;
1223
+ }
1224
+ const match = STANDALONE_SCRIPT_COMMAND.exec(hook.command);
1225
+ return (match?.[1] ?? match?.[2]) === "scripts/rtk-intercept.sh";
1226
+ }
1227
+ function retireRtkGroup(group, event) {
1228
+ if (!isRecord(group) || !Array.isArray(group.hooks)) {
1229
+ return [group];
1230
+ }
1231
+ const remaining = group.hooks.filter((hook) => !isRetiredRtkCommand(hook, event, matcherKey(group)));
1232
+ if (remaining.length === group.hooks.length) {
1233
+ return [group];
1234
+ }
1235
+ return remaining.length === 0 ? [] : [{ ...group, hooks: remaining }];
1236
+ }
1237
+ function retireRtkHookBlocks(existing) {
1238
+ if (!isRecord(existing)) {
1239
+ return existing;
1240
+ }
1241
+ const updates = {};
1242
+ for (const [event, value] of Object.entries(existing)) {
1243
+ if (!Array.isArray(value)) {
1244
+ continue;
1245
+ }
1246
+ const residual = value.flatMap((group) => retireRtkGroup(group, event));
1247
+ if (residual.length !== value.length || residual.some((group, index) => group !== value[index])) {
1248
+ updates[event] = residual;
1249
+ }
1250
+ }
1251
+ return Object.keys(updates).length === 0 ? existing : { ...existing, ...updates };
1252
+ }
1253
+ function userResidual(group, event, owned, ownership) {
1238
1254
  if (!isRecord(group) || !Array.isArray(group.hooks)) {
1239
1255
  return [group];
1240
1256
  }
1257
+ const matcher = matcherKey(group);
1258
+ if (group.description === RETIRED_RTK_DESCRIPTION) {
1259
+ return retireRtkGroup(group, event);
1260
+ }
1241
1261
  if (typeof group.description === "string" && ownership.descriptions.has(group.description)) {
1242
1262
  return [];
1243
1263
  }
1244
- const ownedForMatcher = owned?.get(matcherKey(group));
1245
- const remaining = group.hooks.filter((hook) => !isOwnedCommand(hook, ownedForMatcher));
1264
+ const ownedForMatcher = owned?.get(matcher);
1265
+ const remaining = group.hooks.filter((hook) => !isRetiredRtkCommand(hook, event, matcher) && !isOwnedCommand(hook, ownedForMatcher));
1246
1266
  if (remaining.length === group.hooks.length) {
1247
1267
  return [group];
1248
1268
  }
@@ -1265,7 +1285,7 @@ function mergeHookBlocks(existing, generated) {
1265
1285
  continue;
1266
1286
  }
1267
1287
  const owned = ownership.events.get(event);
1268
- const residual = value.flatMap((group) => userResidual(group, owned, ownership));
1288
+ const residual = value.flatMap((group) => userResidual(group, event, owned, ownership));
1269
1289
  if (residual.length === 0) {
1270
1290
  continue;
1271
1291
  }
@@ -2221,8 +2241,23 @@ function sortFilesByPath(files) {
2221
2241
  const sorted = Object.keys(files).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
2222
2242
  return Object.fromEntries(sorted.map((key) => [key, files[key]]));
2223
2243
  }
2244
+ var RETIRED_RTK_PATHS = [
2245
+ ".claude/skills/rtk-exec/SKILL.md",
2246
+ ".claude/skills/rtk-exec/scripts/rtk-wrapper.cjs",
2247
+ ".claude/hooks/scripts/rtk-intercept.sh"
2248
+ ];
2249
+ function getRetiredPaths(options) {
2250
+ const retiredPaths = new Set([...RETIRED_RTK_PATHS, ...options.retiredPaths ?? []]);
2251
+ for (const path of retiredPaths) {
2252
+ if (typeof path !== "string" || /[\\:*?[\]{}\0]/.test(path) || path.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
2253
+ throw new Error("retiredPaths must contain exact canonical relative file paths");
2254
+ }
2255
+ }
2256
+ return retiredPaths;
2257
+ }
2224
2258
  async function generateLockfile(targetDir, generatorVersion, templateVersion, options = {}) {
2225
2259
  const files = {};
2260
+ const retiredPaths = getRetiredPaths(options);
2226
2261
  const gitVisible = options.excludeGitIgnored ? await listGitVisiblePaths(targetDir) : null;
2227
2262
  const componentRoots = COMPONENT_PATHS.map(([prefix]) => join4(targetDir, prefix));
2228
2263
  for (const componentRoot of componentRoots) {
@@ -2231,7 +2266,7 @@ async function generateLockfile(targetDir, generatorVersion, templateVersion, op
2231
2266
  debug("lockfile.component_dir_missing", { path: componentRoot });
2232
2267
  continue;
2233
2268
  }
2234
- const allFiles = await collectFiles(componentRoot, targetDir, false);
2269
+ const allFiles = (await collectFiles(componentRoot, targetDir, false)).filter((absolutePath) => !retiredPaths.has(relative2(targetDir, absolutePath).replace(/\\/g, "/")));
2235
2270
  for (const absolutePath of allFiles) {
2236
2271
  const relativePath = relative2(targetDir, absolutePath).replace(/\\/g, "/");
2237
2272
  if (gitVisible !== null && !isGitVisible(gitVisible, relativePath)) {
@@ -2314,65 +2349,6 @@ async function generateAndWriteLockfileForDir(targetDir, options = {}) {
2314
2349
  }
2315
2350
  }
2316
2351
 
2317
- // src/core/rtk-installer.ts
2318
- import { execSync as execSync2 } from "node:child_process";
2319
- import { platform as platform2 } from "node:os";
2320
- var defaultDeps2 = {
2321
- exec: execSync2,
2322
- getPlatform: platform2
2323
- };
2324
- function isRtkInstalled(deps = defaultDeps2) {
2325
- try {
2326
- deps.exec("which rtk", { stdio: "pipe", timeout: 3000 });
2327
- return true;
2328
- } catch {
2329
- return false;
2330
- }
2331
- }
2332
- function installRtk(deps = defaultDeps2) {
2333
- if (process.env.CI || false || false) {
2334
- return false;
2335
- }
2336
- if (isRtkInstalled(deps)) {
2337
- info("rtk.already_installed");
2338
- return true;
2339
- }
2340
- const os = deps.getPlatform();
2341
- try {
2342
- if (os === "darwin") {
2343
- try {
2344
- info("rtk.installing_brew");
2345
- deps.exec("brew install rtk-ai/tap/rtk", {
2346
- stdio: "inherit",
2347
- timeout: 120000
2348
- });
2349
- return true;
2350
- } catch {
2351
- info("rtk.installing_curl");
2352
- deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
2353
- stdio: "inherit",
2354
- timeout: 120000
2355
- });
2356
- return isRtkInstalled(deps);
2357
- }
2358
- } else if (os === "linux") {
2359
- info("rtk.installing_curl");
2360
- deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
2361
- stdio: "inherit",
2362
- timeout: 120000
2363
- });
2364
- return isRtkInstalled(deps);
2365
- } else {
2366
- warn("rtk.unsupported_os", { os });
2367
- return false;
2368
- }
2369
- } catch (err) {
2370
- const message = err instanceof Error ? err.message : String(err);
2371
- warn("rtk.install_failed", { error: message });
2372
- return false;
2373
- }
2374
- }
2375
-
2376
2352
  // src/core/scope-filter.ts
2377
2353
  function getSkillScope(content) {
2378
2354
  const cleaned = content.replace(/^\uFEFF/, "");
@@ -2661,19 +2637,6 @@ async function updateInstallConfig(targetDir, options, installedComponents) {
2661
2637
  config.installedComponents = installedComponents;
2662
2638
  await saveConfig(targetDir, config);
2663
2639
  }
2664
- function installRtkIfNeeded(result) {
2665
- if (!isRtkInstalled()) {
2666
- info("install.rtk_installing");
2667
- const rtkInstalled = installRtk();
2668
- if (rtkInstalled) {
2669
- info("install.rtk_success");
2670
- } else {
2671
- result.warnings.push("RTK installation failed — install manually: brew install rtk-ai/tap/rtk");
2672
- }
2673
- } else {
2674
- info("install.rtk_already");
2675
- }
2676
- }
2677
2640
  function installCodexIfNeeded(result) {
2678
2641
  if (!isCodexInstalled()) {
2679
2642
  info("install.codex_installing");
@@ -2728,7 +2691,6 @@ async function install(options) {
2728
2691
  } else {
2729
2692
  info("install.lockfile_generated", { files: String(lockfileResult.fileCount) });
2730
2693
  }
2731
- installRtkIfNeeded(result);
2732
2694
  installCodexIfNeeded(result);
2733
2695
  result.success = true;
2734
2696
  success("install.success");
@@ -2936,14 +2898,15 @@ async function detectProvider(_options = {}) {
2936
2898
  };
2937
2899
  }
2938
2900
  // src/core/updater.ts
2939
- import { join as join8 } from "node:path";
2901
+ import { lstat, realpath as realpath2, unlink } from "node:fs/promises";
2902
+ import { isAbsolute as isAbsolute3, join as join8, relative as relative3, resolve as resolve3, sep as sep4 } from "node:path";
2940
2903
  // package.json
2941
2904
  var package_default = {
2942
2905
  name: "oh-my-customcode",
2943
2906
  workspaces: [
2944
2907
  "packages/*"
2945
2908
  ],
2946
- version: "1.1.106",
2909
+ version: "2.0.0",
2947
2910
  description: "Batteries-included agent harness for Claude Code",
2948
2911
  type: "module",
2949
2912
  bin: {
@@ -5589,6 +5552,86 @@ ${MANAGED_END}`;
5589
5552
 
5590
5553
  // src/core/updater.ts
5591
5554
  var CUSTOMIZATION_MANIFEST_FILE = ".omcustom-customizations.json";
5555
+ var RETIRED_RTK_FILES = [
5556
+ { path: ".claude/skills/rtk-exec/SKILL.md", component: "skills" },
5557
+ { path: ".claude/skills/rtk-exec/scripts/rtk-wrapper.cjs", component: "skills" },
5558
+ { path: ".claude/hooks/scripts/rtk-intercept.sh", component: "hooks" }
5559
+ ];
5560
+ function getRtkRetirement(result) {
5561
+ result.rtkRetirement ??= {
5562
+ removed: [],
5563
+ wouldRemove: [],
5564
+ preserved: [],
5565
+ settingsChanged: [],
5566
+ wouldChangeSettings: [],
5567
+ settingsConflicts: []
5568
+ };
5569
+ return result.rtkRetirement;
5570
+ }
5571
+ async function validateRtkTarget(targetDir, path) {
5572
+ const validation = validatePreserveFilePath(path, targetDir);
5573
+ if (!validation.valid)
5574
+ throw new Error("path escapes project");
5575
+ const fullPath = resolve3(targetDir, path);
5576
+ const fileStat = await lstat(fullPath);
5577
+ if (!fileStat.isFile() || fileStat.isSymbolicLink())
5578
+ throw new Error("not a regular owned file");
5579
+ const root = await realpath2(targetDir);
5580
+ const resolvedFile = await realpath2(fullPath);
5581
+ const within = relative3(root, resolvedFile);
5582
+ if (within === "" || within === ".." || within.startsWith(`..${sep4}`) || isAbsolute3(within)) {
5583
+ throw new Error("resolved path escapes project");
5584
+ }
5585
+ }
5586
+ function isRtkPathPreserved(path, config, customizations) {
5587
+ const paths = [
5588
+ ...config.preserveFiles ?? [],
5589
+ ...customizations?.preserveFiles ?? [],
5590
+ ...customizations?.modifiedFiles ?? [],
5591
+ ...(config.customComponents ?? []).map((component) => component.path)
5592
+ ];
5593
+ return paths.some((preserved) => path === preserved || path.startsWith(`${preserved}/`));
5594
+ }
5595
+ async function retireRtkFile(path, options, lockfile, result, preserved) {
5596
+ const retirement = getRtkRetirement(result);
5597
+ try {
5598
+ await validateRtkTarget(options.targetDir, path);
5599
+ if (preserved)
5600
+ throw new Error("explicitly preserved");
5601
+ if (retirement.settingsConflicts.length > 0)
5602
+ throw new Error("settings retirement conflict");
5603
+ const entry = lockfile?.files[path];
5604
+ if (!entry || typeof entry.templateHash !== "string" || !/^[a-f0-9]{64}$/.test(entry.templateHash)) {
5605
+ throw new Error("missing or invalid old ownership");
5606
+ }
5607
+ const fullPath = join8(options.targetDir, path);
5608
+ if (await computeFileHash(fullPath) !== entry.templateHash)
5609
+ throw new Error("user-modified file");
5610
+ if (options.dryRun) {
5611
+ retirement.wouldRemove.push(path);
5612
+ return;
5613
+ }
5614
+ await validateRtkTarget(options.targetDir, path);
5615
+ await unlink(fullPath);
5616
+ retirement.removed.push(path);
5617
+ result.removedDeprecatedFiles.push(path);
5618
+ } catch (err) {
5619
+ if (err.code === "ENOENT")
5620
+ return;
5621
+ const reason = err instanceof Error ? err.message : "unreadable ownership or file";
5622
+ retirement.preserved.push({ path, reason });
5623
+ result.preservedFiles.push(path);
5624
+ result.warnings.push(`RTK retirement preserved ${path}: ${reason}`);
5625
+ }
5626
+ }
5627
+ async function retireRtkFiles(options, lockfile, result, config, customizations) {
5628
+ const fullUpdate = !options.components || options.components.length === 0;
5629
+ const files = RETIRED_RTK_FILES.filter((file) => fullUpdate || options.components?.includes(file.component));
5630
+ for (const file of files) {
5631
+ await retireRtkFile(file.path, options, lockfile, result, isRtkPathPreserved(file.path, config, customizations));
5632
+ }
5633
+ return RETIRED_RTK_FILES.map((file) => file.path);
5634
+ }
5592
5635
  function createUpdateResult() {
5593
5636
  return {
5594
5637
  success: false,
@@ -5776,32 +5819,52 @@ async function backfillStatusLineRefreshInterval(targetDir, options) {
5776
5819
  });
5777
5820
  }
5778
5821
  }
5779
- async function migrateHookCommandsInSettingsLocal(targetDir, options) {
5780
- if (options.dryRun) {
5781
- return;
5822
+ async function migrateHookCommandsInSettingsLocal(targetDir, options, result) {
5823
+ for (const name of ["settings.json", "settings.local.json"]) {
5824
+ await migrateRtkSettingsFile(targetDir, options, result, name);
5782
5825
  }
5826
+ }
5827
+ async function migrateRtkSettingsFile(targetDir, options, result, name) {
5783
5828
  const layout = getProviderLayout();
5784
- const settingsPath = join8(targetDir, layout.rootDir, "settings.local.json");
5785
- if (!await fileExists(settingsPath)) {
5786
- return;
5787
- }
5829
+ const path = `${layout.rootDir}/${name}`;
5830
+ const settingsPath = join8(targetDir, path);
5831
+ const retirement = getRtkRetirement(result);
5788
5832
  try {
5833
+ await validateRtkTarget(targetDir, path);
5789
5834
  const raw = await readTextFile(settingsPath);
5790
5835
  const bom = raw.startsWith("\uFEFF") ? "\uFEFF" : "";
5791
5836
  const parsed = JSON.parse(bom ? raw.slice(bom.length) : raw);
5792
5837
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
5838
+ throw new Error("unknown settings shape");
5839
+ }
5840
+ const current = parsed;
5841
+ const hooks = retireRtkHookBlocks(current.hooks);
5842
+ const retired = hooks !== current.hooks;
5843
+ const input = retired ? { ...current, hooks } : current;
5844
+ const { settings, rewritten } = name === "settings.local.json" ? migrateHookCommands(input) : { settings: input, rewritten: 0 };
5845
+ if (JSON.stringify(settings.hooks)?.includes("rtk-intercept.sh")) {
5846
+ retirement.settingsConflicts.push(path);
5847
+ result.warnings.push(`RTK retirement retained user or unknown wiring in ${path}`);
5848
+ }
5849
+ if (!retired && rewritten === 0) {
5793
5850
  return;
5794
5851
  }
5795
- const { settings, rewritten } = migrateHookCommands(parsed);
5796
- if (rewritten === 0) {
5852
+ if (options.dryRun) {
5853
+ retirement.wouldChangeSettings.push(path);
5797
5854
  return;
5798
5855
  }
5799
5856
  const trailingNewline = raw.endsWith(`
5800
5857
  `) ? `
5801
5858
  ` : "";
5859
+ await validateRtkTarget(targetDir, path);
5802
5860
  await writeTextFile(settingsPath, `${bom}${JSON.stringify(settings, null, 2)}${trailingNewline}`);
5861
+ retirement.settingsChanged.push(path);
5803
5862
  info("update.hook_commands_migrated", { count: String(rewritten) });
5804
- } catch {
5863
+ } catch (err) {
5864
+ if (err.code === "ENOENT")
5865
+ return;
5866
+ retirement.settingsConflicts.push(path);
5867
+ result.warnings.push(`RTK retirement could not safely migrate ${path}`);
5805
5868
  warn("update.hook_commands_migration_failed", {
5806
5869
  path: settingsPath
5807
5870
  });
@@ -5821,7 +5884,7 @@ async function runFullUpdatePostProcessing(options, result, config, customizatio
5821
5884
  }
5822
5885
  }
5823
5886
  if (isFullUpdate || options.components?.includes("hooks")) {
5824
- await migrateHookCommandsInSettingsLocal(options.targetDir, options);
5887
+ await migrateHookCommandsInSettingsLocal(options.targetDir, options, result);
5825
5888
  }
5826
5889
  if (!options.dryRun) {
5827
5890
  config.version = result.newVersion;
@@ -5848,24 +5911,16 @@ function compareSemver(a, b) {
5848
5911
  }
5849
5912
  return 0;
5850
5913
  }
5851
- function checkAndInstallRtkAfterUpdate() {
5852
- if (!isRtkInstalled()) {
5853
- warn("update.rtk_missing");
5854
- console.log(i18n.t("cli.update.rtkMissing"));
5855
- const rtkInstalled = installRtk();
5856
- if (rtkInstalled) {
5857
- console.log(i18n.t("cli.update.rtkInstalled"));
5858
- }
5859
- }
5860
- }
5861
5914
  async function updateProjectRegistry(targetDir, newVersion) {
5862
5915
  try {
5863
5916
  await Promise.resolve().then(() => init_registry());
5864
5917
  await registerProject(targetDir, newVersion);
5865
5918
  } catch {}
5866
5919
  }
5867
- async function regenerateLockfile(targetDir, result) {
5868
- const lockfileResult = await generateAndWriteLockfileForDir(targetDir);
5920
+ async function regenerateLockfile(options, result, retiredPaths) {
5921
+ if (options.dryRun)
5922
+ return;
5923
+ const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir, { retiredPaths });
5869
5924
  if (lockfileResult.warning) {
5870
5925
  result.warnings.push(lockfileResult.warning);
5871
5926
  warn("update.lockfile_failed", { error: lockfileResult.warning });
@@ -5922,7 +5977,7 @@ async function update(options) {
5922
5977
  result.skippedComponents = options.components || getAllUpdateComponents();
5923
5978
  return result;
5924
5979
  }
5925
- await handleBackupIfRequested(options.targetDir, !!options.backup, result);
5980
+ await handleBackupIfRequested(options.targetDir, !!options.backup && !options.dryRun, result);
5926
5981
  const manifestCustomizations = await resolveManifestCustomizations(options, options.targetDir);
5927
5982
  const configPreserveFiles = resolveConfigPreserveFiles(options, config);
5928
5983
  const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles, options.targetDir);
@@ -5930,9 +5985,10 @@ async function update(options) {
5930
5985
  const components = options.components || getAllUpdateComponents();
5931
5986
  await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
5932
5987
  await runFullUpdatePostProcessing(options, result, config, customizations);
5933
- await regenerateLockfile(options.targetDir, result);
5934
- checkAndInstallRtkAfterUpdate();
5935
- checkAndInstallCodexAfterUpdate();
5988
+ const retiredPaths = await retireRtkFiles(options, lockfile, result, config, customizations);
5989
+ await regenerateLockfile(options, result, retiredPaths);
5990
+ if (!options.dryRun)
5991
+ checkAndInstallCodexAfterUpdate();
5936
5992
  if (result.success && !options.dryRun) {
5937
5993
  await updateProjectRegistry(options.targetDir, result.newVersion);
5938
5994
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "1.1.106",
6
+ "version": "2.0.0",
7
7
  "description": "Batteries-included agent harness for Claude Code",
8
8
  "type": "module",
9
9
  "bin": {
@@ -82,16 +82,6 @@
82
82
  ],
83
83
  "description": "Schema-based tool input validation — Phase 1 advisory only"
84
84
  },
85
- {
86
- "matcher": "tool == \"Bash\"",
87
- "hooks": [
88
- {
89
- "type": "command",
90
- "command": "bash \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/scripts/rtk-intercept.sh\""
91
- }
92
- ],
93
- "description": "RTK auto-intercept — transparently rewrites CLI commands through RTK proxy when available (R013 advisory)"
94
- },
95
85
  {
96
86
  "matcher": "tool == \"Bash\" && tool_input.command matches \"git (reset|clean|checkout|restore|switch|rebase|merge)\"",
97
87
  "hooks": [