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/README.md +8 -9
- package/dist/cli/index.js +188 -178
- package/dist/index.js +181 -125
- package/package.json +1 -1
- package/templates/.claude/hooks/hooks.json +0 -10
- package/templates/.claude/hooks/scripts/cost-cap-advisor.sh +107 -52
- package/templates/.claude/hooks/scripts/session-env-check.sh +0 -8
- package/templates/.claude/rules/MUST-agent-design.md +1 -1
- package/templates/.claude/rules/SHOULD-memory-integration.md +1 -1
- package/templates/.claude/settings.json +0 -10
- package/templates/.claude/skills/intent-detection/patterns/agent-triggers.yaml +0 -8
- package/templates/CLAUDE.md +1 -1
- package/templates/CLAUDE.md.en +1 -1
- package/templates/CLAUDE.md.ko +1 -1
- package/templates/README.md +4 -4
- package/templates/guides/external-tools/ecc-absorption-decisions.md +2 -0
- package/templates/guides/multi-provider-exec/README.md +7 -18
- package/templates/manifest.json +3 -3
- package/templates/.claude/hooks/scripts/rtk-intercept.sh +0 -77
- package/templates/.claude/skills/rtk-exec/SKILL.md +0 -199
- package/templates/.claude/skills/rtk-exec/scripts/rtk-wrapper.cjs +0 -377
package/dist/cli/index.js
CHANGED
|
@@ -335,7 +335,7 @@ var init_package = __esm(() => {
|
|
|
335
335
|
workspaces: [
|
|
336
336
|
"packages/*"
|
|
337
337
|
],
|
|
338
|
-
version: "
|
|
338
|
+
version: "2.0.0",
|
|
339
339
|
description: "Batteries-included agent harness for Claude Code",
|
|
340
340
|
type: "module",
|
|
341
341
|
bin: {
|
|
@@ -14556,8 +14556,6 @@ var en_default = {
|
|
|
14556
14556
|
selfUpdateReexec: "↻ Re-executing with updated CLI (v{{version}})...",
|
|
14557
14557
|
reexecArgvMissing: "⚠ Cannot re-exec: process.argv[1] is empty — continuing without re-exec",
|
|
14558
14558
|
allProjectSkippedSource: " ℹ Skipped: {{name}} (source project cannot self-update)",
|
|
14559
|
-
rtkMissing: "[RTK] RTK is not installed. Attempting installation...",
|
|
14560
|
-
rtkInstalled: "[RTK] ✓ RTK installed — 60-90% token savings activated",
|
|
14561
14559
|
codexMissing: "[Codex] Codex CLI is not installed. Attempting installation...",
|
|
14562
14560
|
codexInstalled: "[Codex] ✓ Codex CLI installed"
|
|
14563
14561
|
},
|
|
@@ -14660,10 +14658,6 @@ var en_default = {
|
|
|
14660
14658
|
pass: "Framework is up to date (v{{version}})",
|
|
14661
14659
|
warn: "Framework is outdated: installed v{{installed}}, latest v{{latest}} ({{behind}} version(s) behind). Run 'omcustom update' to sync."
|
|
14662
14660
|
},
|
|
14663
|
-
rtk: {
|
|
14664
|
-
pass: "RTK installed",
|
|
14665
|
-
warn: "RTK not installed — token savings unavailable"
|
|
14666
|
-
},
|
|
14667
14661
|
codex: {
|
|
14668
14662
|
pass: "Codex CLI installed",
|
|
14669
14663
|
warn: "Codex CLI not installed — AI-assisted development unavailable"
|
|
@@ -14736,10 +14730,6 @@ var en_default = {
|
|
|
14736
14730
|
}
|
|
14737
14731
|
},
|
|
14738
14732
|
install: {
|
|
14739
|
-
rtk_installing: "Installing RTK...",
|
|
14740
|
-
rtk_success: "RTK installed successfully",
|
|
14741
|
-
rtk_already: "RTK already installed",
|
|
14742
|
-
rtk_install_failed: "RTK installation failed",
|
|
14743
14733
|
codex_installing: "Installing Codex CLI...",
|
|
14744
14734
|
codex_success: "Codex CLI installed successfully",
|
|
14745
14735
|
codex_already: "Codex CLI already installed",
|
|
@@ -14980,8 +14970,6 @@ var ko_default = {
|
|
|
14980
14970
|
selfUpdateReexec: "↻ 새 CLI(v{{version}})로 재실행 중...",
|
|
14981
14971
|
reexecArgvMissing: "⚠ 재실행 불가: process.argv[1]이 비어있습니다 — 재실행 없이 계속합니다",
|
|
14982
14972
|
allProjectSkippedSource: " ℹ 스킵: {{name}} (소스 프로젝트는 자체 업데이트할 수 없음)",
|
|
14983
|
-
rtkMissing: "[RTK] RTK가 설치되지 않았습니다. 설치를 시도합니다...",
|
|
14984
|
-
rtkInstalled: "[RTK] ✓ RTK 설치 완료 — 토큰 60-90% 절감 활성화",
|
|
14985
14973
|
codexMissing: "[Codex] Codex CLI가 설치되지 않았습니다. 설치를 시도합니다...",
|
|
14986
14974
|
codexInstalled: "[Codex] ✓ Codex CLI 설치 완료"
|
|
14987
14975
|
},
|
|
@@ -15084,10 +15072,6 @@ var ko_default = {
|
|
|
15084
15072
|
pass: "프레임워크가 최신 상태입니다 (v{{version}})",
|
|
15085
15073
|
warn: "프레임워크가 구버전입니다: 설치됨 v{{installed}}, 최신 v{{latest}} ({{behind}}개 버전 뒤처짐). 'omcustom update'를 실행하여 동기화하세요."
|
|
15086
15074
|
},
|
|
15087
|
-
rtk: {
|
|
15088
|
-
pass: "RTK 설치됨",
|
|
15089
|
-
warn: "RTK 미설치 — 토큰 절감 불가"
|
|
15090
|
-
},
|
|
15091
15075
|
codex: {
|
|
15092
15076
|
pass: "Codex CLI 설치됨",
|
|
15093
15077
|
warn: "Codex CLI 미설치 — AI 개발 지원 불가"
|
|
@@ -15160,10 +15144,6 @@ var ko_default = {
|
|
|
15160
15144
|
}
|
|
15161
15145
|
},
|
|
15162
15146
|
install: {
|
|
15163
|
-
rtk_installing: "RTK 설치 중...",
|
|
15164
|
-
rtk_success: "RTK 설치 완료",
|
|
15165
|
-
rtk_already: "RTK 이미 설치됨",
|
|
15166
|
-
rtk_install_failed: "RTK 설치 실패",
|
|
15167
15147
|
codex_installing: "Codex CLI 설치 중...",
|
|
15168
15148
|
codex_success: "Codex CLI 설치 완료",
|
|
15169
15149
|
codex_already: "Codex CLI 이미 설치됨",
|
|
@@ -15834,18 +15814,10 @@ var MESSAGES = {
|
|
|
15834
15814
|
"codex.installing_npm": "Installing Codex CLI via npm...",
|
|
15835
15815
|
"codex.install_failed": "Failed to install Codex CLI: {{error}}",
|
|
15836
15816
|
"codex.unsupported_os": "Codex CLI auto-install is not supported on {{os}}",
|
|
15837
|
-
"rtk.already_installed": "RTK is already installed",
|
|
15838
|
-
"rtk.installing_brew": "Installing RTK via Homebrew...",
|
|
15839
|
-
"rtk.installing_curl": "Installing RTK via install script...",
|
|
15840
|
-
"rtk.install_failed": "Failed to install RTK: {{error}}",
|
|
15841
|
-
"rtk.unsupported_os": "RTK auto-install is not supported on {{os}}",
|
|
15842
15817
|
"mcp.ontology_rag_configured": "ontology-rag MCP server configured successfully",
|
|
15843
15818
|
"install.codex_installing": "Installing Codex CLI...",
|
|
15844
15819
|
"install.codex_success": "Codex CLI installed successfully",
|
|
15845
15820
|
"install.codex_already": "Codex CLI is already installed",
|
|
15846
|
-
"install.rtk_installing": "Installing RTK...",
|
|
15847
|
-
"install.rtk_success": "RTK installed successfully",
|
|
15848
|
-
"install.rtk_already": "RTK is already installed",
|
|
15849
15821
|
"install.preserved": "Preserved {{files}} file(s) and {{dirs}} directory(ies) from the existing installation",
|
|
15850
15822
|
"install.restored": "Restored {{files}} file(s) and {{dirs}} directory(ies) after installation",
|
|
15851
15823
|
"install.backup_failed": "Failed to back up {{path}}: {{error}}",
|
|
@@ -15857,7 +15829,6 @@ var MESSAGES = {
|
|
|
15857
15829
|
"preserve.restore_failed": "Failed to restore file {{file}}: {{error}}",
|
|
15858
15830
|
"preserve_files.invalid_path": "Ignored invalid preserve_files entry from {{source}}: {{path}} ({{reason}})",
|
|
15859
15831
|
"update.codex_missing": "Codex CLI is not installed",
|
|
15860
|
-
"update.rtk_missing": "RTK is not installed",
|
|
15861
15832
|
"update.self_update_skipped": "Skipped update: source project cannot update itself",
|
|
15862
15833
|
"update.deprecated_file_invalid_path": "Skipped deprecated file with invalid path: {{path}} ({{reason}})",
|
|
15863
15834
|
"update.deprecated_file_removed": "Removed deprecated file: {{path}} ({{reason}})",
|
|
@@ -15956,18 +15927,10 @@ var MESSAGES = {
|
|
|
15956
15927
|
"codex.installing_npm": "npm으로 Codex CLI를 설치합니다...",
|
|
15957
15928
|
"codex.install_failed": "Codex CLI 설치에 실패했습니다: {{error}}",
|
|
15958
15929
|
"codex.unsupported_os": "{{os}}에서는 Codex CLI 자동 설치를 지원하지 않습니다",
|
|
15959
|
-
"rtk.already_installed": "RTK가 이미 설치되어 있습니다",
|
|
15960
|
-
"rtk.installing_brew": "Homebrew로 RTK를 설치합니다...",
|
|
15961
|
-
"rtk.installing_curl": "설치 스크립트로 RTK를 설치합니다...",
|
|
15962
|
-
"rtk.install_failed": "RTK 설치에 실패했습니다: {{error}}",
|
|
15963
|
-
"rtk.unsupported_os": "{{os}}에서는 RTK 자동 설치를 지원하지 않습니다",
|
|
15964
15930
|
"mcp.ontology_rag_configured": "ontology-rag MCP 서버 구성을 완료했습니다",
|
|
15965
15931
|
"install.codex_installing": "Codex CLI를 설치합니다...",
|
|
15966
15932
|
"install.codex_success": "Codex CLI를 설치했습니다",
|
|
15967
15933
|
"install.codex_already": "Codex CLI가 이미 설치되어 있습니다",
|
|
15968
|
-
"install.rtk_installing": "RTK를 설치합니다...",
|
|
15969
|
-
"install.rtk_success": "RTK를 설치했습니다",
|
|
15970
|
-
"install.rtk_already": "RTK가 이미 설치되어 있습니다",
|
|
15971
15934
|
"install.preserved": "기존 설치에서 파일 {{files}}개와 디렉토리 {{dirs}}개를 보존했습니다",
|
|
15972
15935
|
"install.restored": "설치 후 파일 {{files}}개와 디렉토리 {{dirs}}개를 복원했습니다",
|
|
15973
15936
|
"install.backup_failed": "{{path}} 백업에 실패했습니다: {{error}}",
|
|
@@ -15979,7 +15942,6 @@ var MESSAGES = {
|
|
|
15979
15942
|
"preserve.restore_failed": "파일 {{file}} 복원에 실패했습니다: {{error}}",
|
|
15980
15943
|
"preserve_files.invalid_path": "{{source}}의 유효하지 않은 preserve_files 항목을 무시했습니다: {{path}} ({{reason}})",
|
|
15981
15944
|
"update.codex_missing": "Codex CLI가 설치되어 있지 않습니다",
|
|
15982
|
-
"update.rtk_missing": "RTK가 설치되어 있지 않습니다",
|
|
15983
15945
|
"update.self_update_skipped": "업데이트를 건너뜁니다: 소스 프로젝트는 스스로 업데이트할 수 없습니다",
|
|
15984
15946
|
"update.deprecated_file_invalid_path": "경로가 유효하지 않아 지원 중단 파일을 건너뜁니다: {{path}} ({{reason}})",
|
|
15985
15947
|
"update.deprecated_file_removed": "지원 중단 파일을 삭제했습니다: {{path}} ({{reason}})",
|
|
@@ -16570,8 +16532,23 @@ function sortFilesByPath(files) {
|
|
|
16570
16532
|
const sorted = Object.keys(files).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
16571
16533
|
return Object.fromEntries(sorted.map((key) => [key, files[key]]));
|
|
16572
16534
|
}
|
|
16535
|
+
var RETIRED_RTK_PATHS = [
|
|
16536
|
+
".claude/skills/rtk-exec/SKILL.md",
|
|
16537
|
+
".claude/skills/rtk-exec/scripts/rtk-wrapper.cjs",
|
|
16538
|
+
".claude/hooks/scripts/rtk-intercept.sh"
|
|
16539
|
+
];
|
|
16540
|
+
function getRetiredPaths(options) {
|
|
16541
|
+
const retiredPaths = new Set([...RETIRED_RTK_PATHS, ...options.retiredPaths ?? []]);
|
|
16542
|
+
for (const path of retiredPaths) {
|
|
16543
|
+
if (typeof path !== "string" || /[\\:*?[\]{}\0]/.test(path) || path.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
16544
|
+
throw new Error("retiredPaths must contain exact canonical relative file paths");
|
|
16545
|
+
}
|
|
16546
|
+
}
|
|
16547
|
+
return retiredPaths;
|
|
16548
|
+
}
|
|
16573
16549
|
async function generateLockfile(targetDir, generatorVersion, templateVersion, options = {}) {
|
|
16574
16550
|
const files = {};
|
|
16551
|
+
const retiredPaths = getRetiredPaths(options);
|
|
16575
16552
|
const gitVisible = options.excludeGitIgnored ? await listGitVisiblePaths(targetDir) : null;
|
|
16576
16553
|
const componentRoots = COMPONENT_PATHS.map(([prefix]) => join7(targetDir, prefix));
|
|
16577
16554
|
for (const componentRoot of componentRoots) {
|
|
@@ -16580,7 +16557,7 @@ async function generateLockfile(targetDir, generatorVersion, templateVersion, op
|
|
|
16580
16557
|
debug("lockfile.component_dir_missing", { path: componentRoot });
|
|
16581
16558
|
continue;
|
|
16582
16559
|
}
|
|
16583
|
-
const allFiles = await collectFiles(componentRoot, targetDir, false);
|
|
16560
|
+
const allFiles = (await collectFiles(componentRoot, targetDir, false)).filter((absolutePath) => !retiredPaths.has(relative2(targetDir, absolutePath).replace(/\\/g, "/")));
|
|
16584
16561
|
for (const absolutePath of allFiles) {
|
|
16585
16562
|
const relativePath = relative2(targetDir, absolutePath).replace(/\\/g, "/");
|
|
16586
16563
|
if (gitVisible !== null && !isGitVisible(gitVisible, relativePath)) {
|
|
@@ -16686,72 +16663,6 @@ function diffLockfiles(base, current) {
|
|
|
16686
16663
|
return { added, removed, modified, unchanged };
|
|
16687
16664
|
}
|
|
16688
16665
|
|
|
16689
|
-
// src/core/rtk-installer.ts
|
|
16690
|
-
import { execSync as execSync4 } from "node:child_process";
|
|
16691
|
-
import { platform as platform2 } from "node:os";
|
|
16692
|
-
var defaultDeps2 = {
|
|
16693
|
-
exec: execSync4,
|
|
16694
|
-
getPlatform: platform2
|
|
16695
|
-
};
|
|
16696
|
-
function isRtkInstalled(deps = defaultDeps2) {
|
|
16697
|
-
try {
|
|
16698
|
-
deps.exec("which rtk", { stdio: "pipe", timeout: 3000 });
|
|
16699
|
-
return true;
|
|
16700
|
-
} catch {
|
|
16701
|
-
return false;
|
|
16702
|
-
}
|
|
16703
|
-
}
|
|
16704
|
-
function getRtkVersion(deps = defaultDeps2) {
|
|
16705
|
-
try {
|
|
16706
|
-
return deps.exec("rtk --version", { encoding: "utf-8", stdio: "pipe", timeout: 3000 }).trim();
|
|
16707
|
-
} catch {
|
|
16708
|
-
return null;
|
|
16709
|
-
}
|
|
16710
|
-
}
|
|
16711
|
-
function installRtk(deps = defaultDeps2) {
|
|
16712
|
-
if (process.env.CI || false || false) {
|
|
16713
|
-
return false;
|
|
16714
|
-
}
|
|
16715
|
-
if (isRtkInstalled(deps)) {
|
|
16716
|
-
info("rtk.already_installed");
|
|
16717
|
-
return true;
|
|
16718
|
-
}
|
|
16719
|
-
const os = deps.getPlatform();
|
|
16720
|
-
try {
|
|
16721
|
-
if (os === "darwin") {
|
|
16722
|
-
try {
|
|
16723
|
-
info("rtk.installing_brew");
|
|
16724
|
-
deps.exec("brew install rtk-ai/tap/rtk", {
|
|
16725
|
-
stdio: "inherit",
|
|
16726
|
-
timeout: 120000
|
|
16727
|
-
});
|
|
16728
|
-
return true;
|
|
16729
|
-
} catch {
|
|
16730
|
-
info("rtk.installing_curl");
|
|
16731
|
-
deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
|
|
16732
|
-
stdio: "inherit",
|
|
16733
|
-
timeout: 120000
|
|
16734
|
-
});
|
|
16735
|
-
return isRtkInstalled(deps);
|
|
16736
|
-
}
|
|
16737
|
-
} else if (os === "linux") {
|
|
16738
|
-
info("rtk.installing_curl");
|
|
16739
|
-
deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
|
|
16740
|
-
stdio: "inherit",
|
|
16741
|
-
timeout: 120000
|
|
16742
|
-
});
|
|
16743
|
-
return isRtkInstalled(deps);
|
|
16744
|
-
} else {
|
|
16745
|
-
warn("rtk.unsupported_os", { os });
|
|
16746
|
-
return false;
|
|
16747
|
-
}
|
|
16748
|
-
} catch (err) {
|
|
16749
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
16750
|
-
warn("rtk.install_failed", { error: message });
|
|
16751
|
-
return false;
|
|
16752
|
-
}
|
|
16753
|
-
}
|
|
16754
|
-
|
|
16755
16666
|
// src/cli/doctor.ts
|
|
16756
16667
|
async function pathExists(targetPath) {
|
|
16757
16668
|
try {
|
|
@@ -17051,23 +16962,6 @@ async function checkHooks(targetDir, rootDir = ".claude") {
|
|
|
17051
16962
|
fixable: false
|
|
17052
16963
|
};
|
|
17053
16964
|
}
|
|
17054
|
-
async function checkRtk() {
|
|
17055
|
-
if (!isRtkInstalled()) {
|
|
17056
|
-
return {
|
|
17057
|
-
name: "RTK",
|
|
17058
|
-
status: "warn",
|
|
17059
|
-
message: "RTK not installed — token savings unavailable (brew install rtk-ai/tap/rtk)",
|
|
17060
|
-
fixable: true
|
|
17061
|
-
};
|
|
17062
|
-
}
|
|
17063
|
-
const version = getRtkVersion();
|
|
17064
|
-
return {
|
|
17065
|
-
name: "RTK",
|
|
17066
|
-
status: "pass",
|
|
17067
|
-
message: `RTK OK (${version ?? "unknown version"})`,
|
|
17068
|
-
fixable: false
|
|
17069
|
-
};
|
|
17070
|
-
}
|
|
17071
16965
|
async function checkCodex() {
|
|
17072
16966
|
if (!isCodexInstalled()) {
|
|
17073
16967
|
return {
|
|
@@ -17188,7 +17082,6 @@ async function fixSingleIssue(check, targetDir, rootDir = ".claude") {
|
|
|
17188
17082
|
const fixedCount = await fixBrokenSymlinks(targetDir, fullPaths);
|
|
17189
17083
|
return fixedCount > 0;
|
|
17190
17084
|
},
|
|
17191
|
-
RTK: async () => Promise.resolve(installRtk()),
|
|
17192
17085
|
Codex: async () => Promise.resolve(installCodex())
|
|
17193
17086
|
};
|
|
17194
17087
|
const fixer = fixMap[check.name];
|
|
@@ -17338,7 +17231,6 @@ async function runAllChecks(targetDir, layout, packageVersion, includeUpdates) {
|
|
|
17338
17231
|
checkHooks(targetDir, layout.rootDir),
|
|
17339
17232
|
checkContexts(targetDir, layout.rootDir),
|
|
17340
17233
|
checkCustomComponents(targetDir, layout.rootDir),
|
|
17341
|
-
checkRtk(),
|
|
17342
17234
|
checkCodex()
|
|
17343
17235
|
]);
|
|
17344
17236
|
const frameworkCheck = await checkFrameworkDrift(targetDir, packageVersion);
|
|
@@ -17427,6 +17319,7 @@ var LEGACY_OMCUSTOM_DESCRIPTIONS = [
|
|
|
17427
17319
|
];
|
|
17428
17320
|
var STANDALONE_SCRIPT_COMMAND = /^(?:bash )?(?:"(?:\$\{CLAUDE_PROJECT_DIR:-\.\}\/|\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)"|(?:\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh))$/;
|
|
17429
17321
|
var SCRIPT_REFERENCE = /\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)/g;
|
|
17322
|
+
var RETIRED_RTK_DESCRIPTION = "RTK auto-intercept — transparently rewrites CLI commands through RTK proxy when available (R013 advisory)";
|
|
17430
17323
|
function matcherKey(group) {
|
|
17431
17324
|
return typeof group.matcher === "string" ? group.matcher : "";
|
|
17432
17325
|
}
|
|
@@ -17488,15 +17381,52 @@ function isOwnedCommand(hook, owned) {
|
|
|
17488
17381
|
const script = match?.[1] ?? match?.[2];
|
|
17489
17382
|
return script !== undefined && owned.scripts.has(script);
|
|
17490
17383
|
}
|
|
17491
|
-
function
|
|
17384
|
+
function isRetiredRtkCommand(hook, event, matcher) {
|
|
17385
|
+
if (event !== "PreToolUse" || matcher !== "Bash" || !isRecord(hook) || hook.type !== "command" || typeof hook.command !== "string") {
|
|
17386
|
+
return false;
|
|
17387
|
+
}
|
|
17388
|
+
const match = STANDALONE_SCRIPT_COMMAND.exec(hook.command);
|
|
17389
|
+
return (match?.[1] ?? match?.[2]) === "scripts/rtk-intercept.sh";
|
|
17390
|
+
}
|
|
17391
|
+
function retireRtkGroup(group, event) {
|
|
17492
17392
|
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
17493
17393
|
return [group];
|
|
17494
17394
|
}
|
|
17395
|
+
const remaining = group.hooks.filter((hook) => !isRetiredRtkCommand(hook, event, matcherKey(group)));
|
|
17396
|
+
if (remaining.length === group.hooks.length) {
|
|
17397
|
+
return [group];
|
|
17398
|
+
}
|
|
17399
|
+
return remaining.length === 0 ? [] : [{ ...group, hooks: remaining }];
|
|
17400
|
+
}
|
|
17401
|
+
function retireRtkHookBlocks(existing) {
|
|
17402
|
+
if (!isRecord(existing)) {
|
|
17403
|
+
return existing;
|
|
17404
|
+
}
|
|
17405
|
+
const updates = {};
|
|
17406
|
+
for (const [event, value] of Object.entries(existing)) {
|
|
17407
|
+
if (!Array.isArray(value)) {
|
|
17408
|
+
continue;
|
|
17409
|
+
}
|
|
17410
|
+
const residual = value.flatMap((group) => retireRtkGroup(group, event));
|
|
17411
|
+
if (residual.length !== value.length || residual.some((group, index) => group !== value[index])) {
|
|
17412
|
+
updates[event] = residual;
|
|
17413
|
+
}
|
|
17414
|
+
}
|
|
17415
|
+
return Object.keys(updates).length === 0 ? existing : { ...existing, ...updates };
|
|
17416
|
+
}
|
|
17417
|
+
function userResidual(group, event, owned, ownership) {
|
|
17418
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
17419
|
+
return [group];
|
|
17420
|
+
}
|
|
17421
|
+
const matcher = matcherKey(group);
|
|
17422
|
+
if (group.description === RETIRED_RTK_DESCRIPTION) {
|
|
17423
|
+
return retireRtkGroup(group, event);
|
|
17424
|
+
}
|
|
17495
17425
|
if (typeof group.description === "string" && ownership.descriptions.has(group.description)) {
|
|
17496
17426
|
return [];
|
|
17497
17427
|
}
|
|
17498
|
-
const ownedForMatcher = owned?.get(
|
|
17499
|
-
const remaining = group.hooks.filter((hook) => !isOwnedCommand(hook, ownedForMatcher));
|
|
17428
|
+
const ownedForMatcher = owned?.get(matcher);
|
|
17429
|
+
const remaining = group.hooks.filter((hook) => !isRetiredRtkCommand(hook, event, matcher) && !isOwnedCommand(hook, ownedForMatcher));
|
|
17500
17430
|
if (remaining.length === group.hooks.length) {
|
|
17501
17431
|
return [group];
|
|
17502
17432
|
}
|
|
@@ -17519,7 +17449,7 @@ function mergeHookBlocks(existing, generated) {
|
|
|
17519
17449
|
continue;
|
|
17520
17450
|
}
|
|
17521
17451
|
const owned = ownership.events.get(event);
|
|
17522
|
-
const residual = value.flatMap((group) => userResidual(group, owned, ownership));
|
|
17452
|
+
const residual = value.flatMap((group) => userResidual(group, event, owned, ownership));
|
|
17523
17453
|
if (residual.length === 0) {
|
|
17524
17454
|
continue;
|
|
17525
17455
|
}
|
|
@@ -18790,19 +18720,6 @@ async function updateInstallConfig(targetDir, options, installedComponents) {
|
|
|
18790
18720
|
config.installedComponents = installedComponents;
|
|
18791
18721
|
await saveConfig(targetDir, config);
|
|
18792
18722
|
}
|
|
18793
|
-
function installRtkIfNeeded(result) {
|
|
18794
|
-
if (!isRtkInstalled()) {
|
|
18795
|
-
info("install.rtk_installing");
|
|
18796
|
-
const rtkInstalled = installRtk();
|
|
18797
|
-
if (rtkInstalled) {
|
|
18798
|
-
info("install.rtk_success");
|
|
18799
|
-
} else {
|
|
18800
|
-
result.warnings.push("RTK installation failed — install manually: brew install rtk-ai/tap/rtk");
|
|
18801
|
-
}
|
|
18802
|
-
} else {
|
|
18803
|
-
info("install.rtk_already");
|
|
18804
|
-
}
|
|
18805
|
-
}
|
|
18806
18723
|
function installCodexIfNeeded(result) {
|
|
18807
18724
|
if (!isCodexInstalled()) {
|
|
18808
18725
|
info("install.codex_installing");
|
|
@@ -18857,7 +18774,6 @@ async function install(options) {
|
|
|
18857
18774
|
} else {
|
|
18858
18775
|
info("install.lockfile_generated", { files: String(lockfileResult.fileCount) });
|
|
18859
18776
|
}
|
|
18860
|
-
installRtkIfNeeded(result);
|
|
18861
18777
|
installCodexIfNeeded(result);
|
|
18862
18778
|
result.success = true;
|
|
18863
18779
|
success("install.success");
|
|
@@ -19089,7 +19005,7 @@ async function generateMCPConfig(targetDir) {
|
|
|
19089
19005
|
|
|
19090
19006
|
// src/core/ontology-rag-setup.ts
|
|
19091
19007
|
init_fs();
|
|
19092
|
-
import { execSync as
|
|
19008
|
+
import { execSync as execSync4 } from "node:child_process";
|
|
19093
19009
|
import { join as join11 } from "node:path";
|
|
19094
19010
|
var MIN_PYTHON_MINOR = 10;
|
|
19095
19011
|
var DETECTION_TIMEOUT_MS = 3000;
|
|
@@ -19102,7 +19018,7 @@ function parsePythonVersion(output) {
|
|
|
19102
19018
|
}
|
|
19103
19019
|
function checkPython3() {
|
|
19104
19020
|
try {
|
|
19105
|
-
const output =
|
|
19021
|
+
const output = execSync4("python3 --version 2>&1", {
|
|
19106
19022
|
stdio: "pipe",
|
|
19107
19023
|
timeout: DETECTION_TIMEOUT_MS
|
|
19108
19024
|
}).toString().trim();
|
|
@@ -19119,21 +19035,21 @@ function checkPython3() {
|
|
|
19119
19035
|
}
|
|
19120
19036
|
function checkUvAvailableForSetup() {
|
|
19121
19037
|
try {
|
|
19122
|
-
|
|
19038
|
+
execSync4("uv --version", { stdio: "pipe", timeout: DETECTION_TIMEOUT_MS });
|
|
19123
19039
|
return true;
|
|
19124
19040
|
} catch {
|
|
19125
19041
|
return false;
|
|
19126
19042
|
}
|
|
19127
19043
|
}
|
|
19128
19044
|
function createVenvWithUv(targetDir) {
|
|
19129
|
-
|
|
19045
|
+
execSync4("uv venv --python 3.12 .venv", {
|
|
19130
19046
|
cwd: targetDir,
|
|
19131
19047
|
stdio: "pipe",
|
|
19132
19048
|
timeout: INSTALL_TIMEOUT_MS
|
|
19133
19049
|
});
|
|
19134
19050
|
}
|
|
19135
19051
|
function createVenvWithPython3(targetDir) {
|
|
19136
|
-
|
|
19052
|
+
execSync4("python3 -m venv .venv", {
|
|
19137
19053
|
cwd: targetDir,
|
|
19138
19054
|
stdio: "pipe",
|
|
19139
19055
|
timeout: INSTALL_TIMEOUT_MS
|
|
@@ -19143,13 +19059,13 @@ function installOntologyRagEditable(targetDir, useUv) {
|
|
|
19143
19059
|
const packageRoot = getPackageRoot();
|
|
19144
19060
|
const ontologyPkgPath = join11(packageRoot, "packages", "ontology-rag");
|
|
19145
19061
|
if (useUv) {
|
|
19146
|
-
|
|
19062
|
+
execSync4(`uv pip install --python .venv/bin/python -e "${ontologyPkgPath}"`, {
|
|
19147
19063
|
cwd: targetDir,
|
|
19148
19064
|
stdio: "pipe",
|
|
19149
19065
|
timeout: INSTALL_TIMEOUT_MS
|
|
19150
19066
|
});
|
|
19151
19067
|
} else {
|
|
19152
|
-
|
|
19068
|
+
execSync4(`.venv/bin/pip install -e "${ontologyPkgPath}"`, {
|
|
19153
19069
|
cwd: targetDir,
|
|
19154
19070
|
stdio: "pipe",
|
|
19155
19071
|
timeout: INSTALL_TIMEOUT_MS
|
|
@@ -21742,7 +21658,8 @@ import { constants as osConstants } from "node:os";
|
|
|
21742
21658
|
|
|
21743
21659
|
// src/core/updater.ts
|
|
21744
21660
|
init_package();
|
|
21745
|
-
import {
|
|
21661
|
+
import { lstat, realpath as realpath2, unlink as unlink2 } from "node:fs/promises";
|
|
21662
|
+
import { isAbsolute as isAbsolute5, join as join19, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
|
|
21746
21663
|
init_fs();
|
|
21747
21664
|
|
|
21748
21665
|
// src/core/entry-merger.ts
|
|
@@ -21869,6 +21786,86 @@ ${MANAGED_END}`;
|
|
|
21869
21786
|
|
|
21870
21787
|
// src/core/updater.ts
|
|
21871
21788
|
var CUSTOMIZATION_MANIFEST_FILE = ".omcustom-customizations.json";
|
|
21789
|
+
var RETIRED_RTK_FILES = [
|
|
21790
|
+
{ path: ".claude/skills/rtk-exec/SKILL.md", component: "skills" },
|
|
21791
|
+
{ path: ".claude/skills/rtk-exec/scripts/rtk-wrapper.cjs", component: "skills" },
|
|
21792
|
+
{ path: ".claude/hooks/scripts/rtk-intercept.sh", component: "hooks" }
|
|
21793
|
+
];
|
|
21794
|
+
function getRtkRetirement(result) {
|
|
21795
|
+
result.rtkRetirement ??= {
|
|
21796
|
+
removed: [],
|
|
21797
|
+
wouldRemove: [],
|
|
21798
|
+
preserved: [],
|
|
21799
|
+
settingsChanged: [],
|
|
21800
|
+
wouldChangeSettings: [],
|
|
21801
|
+
settingsConflicts: []
|
|
21802
|
+
};
|
|
21803
|
+
return result.rtkRetirement;
|
|
21804
|
+
}
|
|
21805
|
+
async function validateRtkTarget(targetDir, path) {
|
|
21806
|
+
const validation = validatePreserveFilePath(path, targetDir);
|
|
21807
|
+
if (!validation.valid)
|
|
21808
|
+
throw new Error("path escapes project");
|
|
21809
|
+
const fullPath = resolve5(targetDir, path);
|
|
21810
|
+
const fileStat = await lstat(fullPath);
|
|
21811
|
+
if (!fileStat.isFile() || fileStat.isSymbolicLink())
|
|
21812
|
+
throw new Error("not a regular owned file");
|
|
21813
|
+
const root = await realpath2(targetDir);
|
|
21814
|
+
const resolvedFile = await realpath2(fullPath);
|
|
21815
|
+
const within = relative4(root, resolvedFile);
|
|
21816
|
+
if (within === "" || within === ".." || within.startsWith(`..${sep4}`) || isAbsolute5(within)) {
|
|
21817
|
+
throw new Error("resolved path escapes project");
|
|
21818
|
+
}
|
|
21819
|
+
}
|
|
21820
|
+
function isRtkPathPreserved(path, config, customizations) {
|
|
21821
|
+
const paths = [
|
|
21822
|
+
...config.preserveFiles ?? [],
|
|
21823
|
+
...customizations?.preserveFiles ?? [],
|
|
21824
|
+
...customizations?.modifiedFiles ?? [],
|
|
21825
|
+
...(config.customComponents ?? []).map((component) => component.path)
|
|
21826
|
+
];
|
|
21827
|
+
return paths.some((preserved) => path === preserved || path.startsWith(`${preserved}/`));
|
|
21828
|
+
}
|
|
21829
|
+
async function retireRtkFile(path, options, lockfile, result, preserved) {
|
|
21830
|
+
const retirement = getRtkRetirement(result);
|
|
21831
|
+
try {
|
|
21832
|
+
await validateRtkTarget(options.targetDir, path);
|
|
21833
|
+
if (preserved)
|
|
21834
|
+
throw new Error("explicitly preserved");
|
|
21835
|
+
if (retirement.settingsConflicts.length > 0)
|
|
21836
|
+
throw new Error("settings retirement conflict");
|
|
21837
|
+
const entry = lockfile?.files[path];
|
|
21838
|
+
if (!entry || typeof entry.templateHash !== "string" || !/^[a-f0-9]{64}$/.test(entry.templateHash)) {
|
|
21839
|
+
throw new Error("missing or invalid old ownership");
|
|
21840
|
+
}
|
|
21841
|
+
const fullPath = join19(options.targetDir, path);
|
|
21842
|
+
if (await computeFileHash(fullPath) !== entry.templateHash)
|
|
21843
|
+
throw new Error("user-modified file");
|
|
21844
|
+
if (options.dryRun) {
|
|
21845
|
+
retirement.wouldRemove.push(path);
|
|
21846
|
+
return;
|
|
21847
|
+
}
|
|
21848
|
+
await validateRtkTarget(options.targetDir, path);
|
|
21849
|
+
await unlink2(fullPath);
|
|
21850
|
+
retirement.removed.push(path);
|
|
21851
|
+
result.removedDeprecatedFiles.push(path);
|
|
21852
|
+
} catch (err) {
|
|
21853
|
+
if (err.code === "ENOENT")
|
|
21854
|
+
return;
|
|
21855
|
+
const reason = err instanceof Error ? err.message : "unreadable ownership or file";
|
|
21856
|
+
retirement.preserved.push({ path, reason });
|
|
21857
|
+
result.preservedFiles.push(path);
|
|
21858
|
+
result.warnings.push(`RTK retirement preserved ${path}: ${reason}`);
|
|
21859
|
+
}
|
|
21860
|
+
}
|
|
21861
|
+
async function retireRtkFiles(options, lockfile, result, config, customizations) {
|
|
21862
|
+
const fullUpdate = !options.components || options.components.length === 0;
|
|
21863
|
+
const files = RETIRED_RTK_FILES.filter((file) => fullUpdate || options.components?.includes(file.component));
|
|
21864
|
+
for (const file of files) {
|
|
21865
|
+
await retireRtkFile(file.path, options, lockfile, result, isRtkPathPreserved(file.path, config, customizations));
|
|
21866
|
+
}
|
|
21867
|
+
return RETIRED_RTK_FILES.map((file) => file.path);
|
|
21868
|
+
}
|
|
21872
21869
|
function createUpdateResult() {
|
|
21873
21870
|
return {
|
|
21874
21871
|
success: false,
|
|
@@ -22056,32 +22053,52 @@ async function backfillStatusLineRefreshInterval(targetDir, options) {
|
|
|
22056
22053
|
});
|
|
22057
22054
|
}
|
|
22058
22055
|
}
|
|
22059
|
-
async function migrateHookCommandsInSettingsLocal(targetDir, options) {
|
|
22060
|
-
|
|
22061
|
-
|
|
22056
|
+
async function migrateHookCommandsInSettingsLocal(targetDir, options, result) {
|
|
22057
|
+
for (const name of ["settings.json", "settings.local.json"]) {
|
|
22058
|
+
await migrateRtkSettingsFile(targetDir, options, result, name);
|
|
22062
22059
|
}
|
|
22060
|
+
}
|
|
22061
|
+
async function migrateRtkSettingsFile(targetDir, options, result, name) {
|
|
22063
22062
|
const layout = getProviderLayout();
|
|
22064
|
-
const
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
}
|
|
22063
|
+
const path = `${layout.rootDir}/${name}`;
|
|
22064
|
+
const settingsPath = join19(targetDir, path);
|
|
22065
|
+
const retirement = getRtkRetirement(result);
|
|
22068
22066
|
try {
|
|
22067
|
+
await validateRtkTarget(targetDir, path);
|
|
22069
22068
|
const raw = await readTextFile(settingsPath);
|
|
22070
22069
|
const bom = raw.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
22071
22070
|
const parsed = JSON.parse(bom ? raw.slice(bom.length) : raw);
|
|
22072
22071
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
22072
|
+
throw new Error("unknown settings shape");
|
|
22073
|
+
}
|
|
22074
|
+
const current = parsed;
|
|
22075
|
+
const hooks = retireRtkHookBlocks(current.hooks);
|
|
22076
|
+
const retired = hooks !== current.hooks;
|
|
22077
|
+
const input = retired ? { ...current, hooks } : current;
|
|
22078
|
+
const { settings, rewritten } = name === "settings.local.json" ? migrateHookCommands(input) : { settings: input, rewritten: 0 };
|
|
22079
|
+
if (JSON.stringify(settings.hooks)?.includes("rtk-intercept.sh")) {
|
|
22080
|
+
retirement.settingsConflicts.push(path);
|
|
22081
|
+
result.warnings.push(`RTK retirement retained user or unknown wiring in ${path}`);
|
|
22082
|
+
}
|
|
22083
|
+
if (!retired && rewritten === 0) {
|
|
22073
22084
|
return;
|
|
22074
22085
|
}
|
|
22075
|
-
|
|
22076
|
-
|
|
22086
|
+
if (options.dryRun) {
|
|
22087
|
+
retirement.wouldChangeSettings.push(path);
|
|
22077
22088
|
return;
|
|
22078
22089
|
}
|
|
22079
22090
|
const trailingNewline = raw.endsWith(`
|
|
22080
22091
|
`) ? `
|
|
22081
22092
|
` : "";
|
|
22093
|
+
await validateRtkTarget(targetDir, path);
|
|
22082
22094
|
await writeTextFile(settingsPath, `${bom}${JSON.stringify(settings, null, 2)}${trailingNewline}`);
|
|
22095
|
+
retirement.settingsChanged.push(path);
|
|
22083
22096
|
info("update.hook_commands_migrated", { count: String(rewritten) });
|
|
22084
|
-
} catch {
|
|
22097
|
+
} catch (err) {
|
|
22098
|
+
if (err.code === "ENOENT")
|
|
22099
|
+
return;
|
|
22100
|
+
retirement.settingsConflicts.push(path);
|
|
22101
|
+
result.warnings.push(`RTK retirement could not safely migrate ${path}`);
|
|
22085
22102
|
warn("update.hook_commands_migration_failed", {
|
|
22086
22103
|
path: settingsPath
|
|
22087
22104
|
});
|
|
@@ -22101,7 +22118,7 @@ async function runFullUpdatePostProcessing(options, result, config, customizatio
|
|
|
22101
22118
|
}
|
|
22102
22119
|
}
|
|
22103
22120
|
if (isFullUpdate || options.components?.includes("hooks")) {
|
|
22104
|
-
await migrateHookCommandsInSettingsLocal(options.targetDir, options);
|
|
22121
|
+
await migrateHookCommandsInSettingsLocal(options.targetDir, options, result);
|
|
22105
22122
|
}
|
|
22106
22123
|
if (!options.dryRun) {
|
|
22107
22124
|
config.version = result.newVersion;
|
|
@@ -22128,24 +22145,16 @@ function compareSemver2(a, b) {
|
|
|
22128
22145
|
}
|
|
22129
22146
|
return 0;
|
|
22130
22147
|
}
|
|
22131
|
-
function checkAndInstallRtkAfterUpdate() {
|
|
22132
|
-
if (!isRtkInstalled()) {
|
|
22133
|
-
warn("update.rtk_missing");
|
|
22134
|
-
console.log(i18n.t("cli.update.rtkMissing"));
|
|
22135
|
-
const rtkInstalled = installRtk();
|
|
22136
|
-
if (rtkInstalled) {
|
|
22137
|
-
console.log(i18n.t("cli.update.rtkInstalled"));
|
|
22138
|
-
}
|
|
22139
|
-
}
|
|
22140
|
-
}
|
|
22141
22148
|
async function updateProjectRegistry(targetDir, newVersion) {
|
|
22142
22149
|
try {
|
|
22143
22150
|
await Promise.resolve().then(() => init_registry());
|
|
22144
22151
|
await registerProject(targetDir, newVersion);
|
|
22145
22152
|
} catch {}
|
|
22146
22153
|
}
|
|
22147
|
-
async function regenerateLockfile(
|
|
22148
|
-
|
|
22154
|
+
async function regenerateLockfile(options, result, retiredPaths) {
|
|
22155
|
+
if (options.dryRun)
|
|
22156
|
+
return;
|
|
22157
|
+
const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir, { retiredPaths });
|
|
22149
22158
|
if (lockfileResult.warning) {
|
|
22150
22159
|
result.warnings.push(lockfileResult.warning);
|
|
22151
22160
|
warn("update.lockfile_failed", { error: lockfileResult.warning });
|
|
@@ -22202,7 +22211,7 @@ async function update(options) {
|
|
|
22202
22211
|
result.skippedComponents = options.components || getAllUpdateComponents();
|
|
22203
22212
|
return result;
|
|
22204
22213
|
}
|
|
22205
|
-
await handleBackupIfRequested(options.targetDir, !!options.backup, result);
|
|
22214
|
+
await handleBackupIfRequested(options.targetDir, !!options.backup && !options.dryRun, result);
|
|
22206
22215
|
const manifestCustomizations = await resolveManifestCustomizations(options, options.targetDir);
|
|
22207
22216
|
const configPreserveFiles = resolveConfigPreserveFiles(options, config);
|
|
22208
22217
|
const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles, options.targetDir);
|
|
@@ -22210,9 +22219,10 @@ async function update(options) {
|
|
|
22210
22219
|
const components = options.components || getAllUpdateComponents();
|
|
22211
22220
|
await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
|
|
22212
22221
|
await runFullUpdatePostProcessing(options, result, config, customizations);
|
|
22213
|
-
await
|
|
22214
|
-
|
|
22215
|
-
|
|
22222
|
+
const retiredPaths = await retireRtkFiles(options, lockfile, result, config, customizations);
|
|
22223
|
+
await regenerateLockfile(options, result, retiredPaths);
|
|
22224
|
+
if (!options.dryRun)
|
|
22225
|
+
checkAndInstallCodexAfterUpdate();
|
|
22216
22226
|
if (result.success && !options.dryRun) {
|
|
22217
22227
|
await updateProjectRegistry(options.targetDir, result.newVersion);
|
|
22218
22228
|
}
|