oh-my-customcode 1.1.106 → 1.1.107
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 +227 -191
- package/dist/index.js +220 -138
- package/package.json +1 -1
- package/templates/.claude/hooks/hooks.json +0 -10
- package/templates/.claude/hooks/scripts/claude-md-reinject.sh +7 -1
- 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/hooks/scripts/session-reflection.sh +60 -15
- package/templates/.claude/hooks/scripts/stuck-detector.sh +110 -14
- package/templates/.claude/rules/MAY-optimization.md +1 -1
- package/templates/.claude/rules/MUST-agent-design.md +1 -1
- package/templates/.claude/rules/SHOULD-memory-integration.md +1 -1
- package/templates/.claude/rules/SHOULD-verification-ladder.md +9 -0
- package/templates/.claude/settings.json +0 -10
- package/templates/.claude/skills/intent-detection/patterns/agent-triggers.yaml +0 -8
- package/templates/.claude/skills/pipeline/workflows/auto-dev.yaml +3 -1
- 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/workflows/auto-dev.yaml +3 -1
- 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: "1.1.
|
|
338
|
+
version: "1.1.107",
|
|
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 이미 설치됨",
|
|
@@ -15820,7 +15800,7 @@ var MESSAGES = {
|
|
|
15820
15800
|
"update.file_applied": "Applied update to {{path}}",
|
|
15821
15801
|
"update.lockfile_regenerated": "Lockfile regenerated ({{files}} files tracked)",
|
|
15822
15802
|
"update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
|
|
15823
|
-
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}}
|
|
15803
|
+
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} selected for update: The protected-file policy allows this template update.",
|
|
15824
15804
|
"update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
|
|
15825
15805
|
"update.hook_commands_migrated": "Migrated {{count}} hook/statusLine command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
15826
15806
|
"update.hook_commands_migration_failed": "Failed to migrate hook commands in {{path}}",
|
|
@@ -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}})",
|
|
@@ -15865,8 +15836,8 @@ var MESSAGES = {
|
|
|
15865
15836
|
"update.entry_doc_force_updated": "Entry document force-updated: {{path}}",
|
|
15866
15837
|
"update.entry_merge_warning": "Entry document merge warning: {{warning}}",
|
|
15867
15838
|
"update.entry_template_not_found": "Entry document template not found: {{template}}",
|
|
15868
|
-
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}}
|
|
15869
|
-
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped:
|
|
15839
|
+
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}} selected for overwrite: The --force-overwrite-all option bypasses the protected-file preservation policy.",
|
|
15840
|
+
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped: The protected-file preservation policy skips this update. Use --force-overwrite-all to override.",
|
|
15870
15841
|
"update.settings_local_backfill_failed": "Failed to backfill statusLine refreshInterval in {{path}}",
|
|
15871
15842
|
"general.done": "Done!",
|
|
15872
15843
|
"general.failed": "Failed",
|
|
@@ -15942,7 +15913,7 @@ var MESSAGES = {
|
|
|
15942
15913
|
"update.file_applied": "{{path}} 업데이트 적용",
|
|
15943
15914
|
"update.lockfile_regenerated": "잠금 파일 재생성 완료 ({{files}}개 파일 추적)",
|
|
15944
15915
|
"update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
|
|
15945
|
-
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}})
|
|
15916
|
+
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트 대상: 보호 파일 정책에 따라 템플릿 업데이트가 허용됩니다.",
|
|
15946
15917
|
"update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
|
|
15947
15918
|
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로 훅/상태줄 명령 {{count}}개를 마이그레이션했습니다",
|
|
15948
15919
|
"update.hook_commands_migration_failed": "{{path}}의 훅 명령 마이그레이션에 실패했습니다",
|
|
@@ -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}})",
|
|
@@ -15987,8 +15949,8 @@ var MESSAGES = {
|
|
|
15987
15949
|
"update.entry_doc_force_updated": "진입 문서를 강제로 업데이트했습니다: {{path}}",
|
|
15988
15950
|
"update.entry_merge_warning": "진입 문서 병합 경고: {{warning}}",
|
|
15989
15951
|
"update.entry_template_not_found": "진입 문서 템플릿을 찾을 수 없습니다: {{template}}",
|
|
15990
|
-
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}}
|
|
15991
|
-
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다:
|
|
15952
|
+
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}} 덮어쓰기 대상: --force-overwrite-all 옵션에 따라 보호 파일 보존 정책을 적용하지 않습니다.",
|
|
15953
|
+
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다: 보호 파일 보존 정책에 따라 업데이트를 건너뜁니다. 덮어쓰려면 --force-overwrite-all 옵션을 사용하십시오.",
|
|
15992
15954
|
"update.settings_local_backfill_failed": "{{path}}의 statusLine refreshInterval 보충에 실패했습니다",
|
|
15993
15955
|
"general.done": "완료!",
|
|
15994
15956
|
"general.failed": "실패",
|
|
@@ -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) {
|
|
17392
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
17393
|
+
return [group];
|
|
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) {
|
|
17492
17418
|
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
17493
17419
|
return [group];
|
|
17494
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;
|
|
@@ -22118,6 +22135,29 @@ async function runFullUpdatePostProcessing(options, result, config, customizatio
|
|
|
22118
22135
|
});
|
|
22119
22136
|
}
|
|
22120
22137
|
}
|
|
22138
|
+
async function getUpdateVersionDecision(installedVersion, cliVersion) {
|
|
22139
|
+
if (!(installedVersion !== "0.0.0" && compareSemver2(installedVersion, cliVersion) > 0)) {
|
|
22140
|
+
return "standard";
|
|
22141
|
+
}
|
|
22142
|
+
if (installedVersion !== "2.0.0") {
|
|
22143
|
+
return "blocked";
|
|
22144
|
+
}
|
|
22145
|
+
const match = /^1\.1\.(0|[1-9]\d*)$/.exec(cliVersion);
|
|
22146
|
+
if (!match) {
|
|
22147
|
+
return "blocked";
|
|
22148
|
+
}
|
|
22149
|
+
const patch = Number(match[1]);
|
|
22150
|
+
if (!Number.isSafeInteger(patch) || patch < 107) {
|
|
22151
|
+
return "blocked";
|
|
22152
|
+
}
|
|
22153
|
+
const manifestPath = resolveTemplatePath(getProviderLayout().manifestFile);
|
|
22154
|
+
if (!await fileExists(manifestPath)) {
|
|
22155
|
+
return "blocked";
|
|
22156
|
+
}
|
|
22157
|
+
const manifest = await readJsonFile(manifestPath);
|
|
22158
|
+
const correctionAllowed = typeof manifest === "object" && manifest !== null && !Array.isArray(manifest) && "version" in manifest && manifest.version === cliVersion;
|
|
22159
|
+
return correctionAllowed ? "correction" : "blocked";
|
|
22160
|
+
}
|
|
22121
22161
|
function compareSemver2(a, b) {
|
|
22122
22162
|
const pa = a.split(".").map(Number);
|
|
22123
22163
|
const pb = b.split(".").map(Number);
|
|
@@ -22128,24 +22168,16 @@ function compareSemver2(a, b) {
|
|
|
22128
22168
|
}
|
|
22129
22169
|
return 0;
|
|
22130
22170
|
}
|
|
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
22171
|
async function updateProjectRegistry(targetDir, newVersion) {
|
|
22142
22172
|
try {
|
|
22143
22173
|
await Promise.resolve().then(() => init_registry());
|
|
22144
22174
|
await registerProject(targetDir, newVersion);
|
|
22145
22175
|
} catch {}
|
|
22146
22176
|
}
|
|
22147
|
-
async function regenerateLockfile(
|
|
22148
|
-
|
|
22177
|
+
async function regenerateLockfile(options, result, retiredPaths) {
|
|
22178
|
+
if (options.dryRun)
|
|
22179
|
+
return;
|
|
22180
|
+
const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir, { retiredPaths });
|
|
22149
22181
|
if (lockfileResult.warning) {
|
|
22150
22182
|
result.warnings.push(lockfileResult.warning);
|
|
22151
22183
|
warn("update.lockfile_failed", { error: lockfileResult.warning });
|
|
@@ -22186,7 +22218,8 @@ async function update(options) {
|
|
|
22186
22218
|
const config = await loadConfig(options.targetDir);
|
|
22187
22219
|
result.previousVersion = config.version;
|
|
22188
22220
|
const cliVersion = package_default.version;
|
|
22189
|
-
|
|
22221
|
+
const versionDecision = await getUpdateVersionDecision(result.previousVersion, cliVersion);
|
|
22222
|
+
if (versionDecision === "blocked") {
|
|
22190
22223
|
result.success = false;
|
|
22191
22224
|
result.error = `Downgrade prevented: project has v${result.previousVersion} but CLI is v${cliVersion}. Update the CLI first: npm install -g oh-my-customcode@latest`;
|
|
22192
22225
|
return result;
|
|
@@ -22195,6 +22228,11 @@ async function update(options) {
|
|
|
22195
22228
|
return result;
|
|
22196
22229
|
}
|
|
22197
22230
|
const updateCheck = await checkForUpdates(options.targetDir);
|
|
22231
|
+
if (versionDecision === "correction" && updateCheck.latestVersion !== cliVersion) {
|
|
22232
|
+
result.success = false;
|
|
22233
|
+
result.error = `Version correction prevented: CLI is v${cliVersion} but templates are v${updateCheck.latestVersion}. Reinstall the CLI: npm install -g oh-my-customcode@latest`;
|
|
22234
|
+
return result;
|
|
22235
|
+
}
|
|
22198
22236
|
result.newVersion = updateCheck.latestVersion;
|
|
22199
22237
|
if (!updateCheck.hasUpdates && !options.force) {
|
|
22200
22238
|
info("update.no_updates");
|
|
@@ -22202,7 +22240,7 @@ async function update(options) {
|
|
|
22202
22240
|
result.skippedComponents = options.components || getAllUpdateComponents();
|
|
22203
22241
|
return result;
|
|
22204
22242
|
}
|
|
22205
|
-
await handleBackupIfRequested(options.targetDir, !!options.backup, result);
|
|
22243
|
+
await handleBackupIfRequested(options.targetDir, !!options.backup && !options.dryRun, result);
|
|
22206
22244
|
const manifestCustomizations = await resolveManifestCustomizations(options, options.targetDir);
|
|
22207
22245
|
const configPreserveFiles = resolveConfigPreserveFiles(options, config);
|
|
22208
22246
|
const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles, options.targetDir);
|
|
@@ -22210,9 +22248,10 @@ async function update(options) {
|
|
|
22210
22248
|
const components = options.components || getAllUpdateComponents();
|
|
22211
22249
|
await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
|
|
22212
22250
|
await runFullUpdatePostProcessing(options, result, config, customizations);
|
|
22213
|
-
await
|
|
22214
|
-
|
|
22215
|
-
|
|
22251
|
+
const retiredPaths = await retireRtkFiles(options, lockfile, result, config, customizations);
|
|
22252
|
+
await regenerateLockfile(options, result, retiredPaths);
|
|
22253
|
+
if (!options.dryRun)
|
|
22254
|
+
checkAndInstallCodexAfterUpdate();
|
|
22216
22255
|
if (result.success && !options.dryRun) {
|
|
22217
22256
|
await updateProjectRegistry(options.targetDir, result.newVersion);
|
|
22218
22257
|
}
|
|
@@ -22367,22 +22406,19 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
22367
22406
|
if (options.forceOverwriteAll) {
|
|
22368
22407
|
warn("update.protected_file_force_overwrite", {
|
|
22369
22408
|
file: protectedPath,
|
|
22370
|
-
component
|
|
22371
|
-
hint: "File contains AI behavioral constraints. Overwriting because --force-overwrite-all was set."
|
|
22409
|
+
component
|
|
22372
22410
|
});
|
|
22373
22411
|
} else {
|
|
22374
22412
|
warn("update.protected_file_skipped", {
|
|
22375
22413
|
file: protectedPath,
|
|
22376
|
-
component
|
|
22377
|
-
hint: "File was modified by user and preserved. Use --force-overwrite-all to override."
|
|
22414
|
+
component
|
|
22378
22415
|
});
|
|
22379
22416
|
}
|
|
22380
22417
|
}
|
|
22381
22418
|
for (const updatedPath of protectedUpdatedPaths) {
|
|
22382
22419
|
info("update.protected_file_updated", {
|
|
22383
22420
|
file: updatedPath,
|
|
22384
|
-
component
|
|
22385
|
-
hint: "Protected file updated (unmodified by user, matches lockfile hash)."
|
|
22421
|
+
component
|
|
22386
22422
|
});
|
|
22387
22423
|
}
|
|
22388
22424
|
skipPaths.push(...protectedSkipPaths);
|