oh-my-customcode 1.1.93 → 1.1.95
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 +1 -1
- package/dist/cli/index.js +435 -63
- package/dist/index.js +430 -58
- package/package.json +1 -1
- package/templates/.claude/agents/mgr-gitnerd.md +8 -1
- package/templates/.claude/hooks/scripts/adaptive-harness-scan.sh +2 -1
- package/templates/.claude/hooks/scripts/claude-md-reinject.sh +2 -1
- package/templates/.claude/hooks/scripts/omcustom-auto-update.sh +7 -0
- package/templates/.claude/hooks/scripts/rule-deletion-guard.sh +890 -45
- package/templates/.claude/hooks/scripts/session-autofix.sh +7 -0
- package/templates/.claude/hooks/scripts/session-env-check.sh +7 -0
- package/templates/.claude/hooks/scripts/stale-todo-scanner.sh +7 -0
- package/templates/.claude/rules/SHOULD-hud-statusline.md +2 -2
- package/templates/.claude/skills/claude-native/SKILL.md +2 -2
- package/templates/.claude/skills/homework/SKILL.md +2 -0
- package/templates/.claude/skills/pipeline/workflows/auto-dev.yaml +18 -4
- package/templates/.claude/skills/playwright-compress/SKILL.md +2 -2
- package/templates/guides/middleware-patterns/README.md +3 -3
- package/templates/manifest.json +1 -1
- package/templates/workflows/auto-dev.yaml +18 -4
package/dist/cli/index.js
CHANGED
|
@@ -81,6 +81,9 @@ function registryDir() {
|
|
|
81
81
|
function registryPath() {
|
|
82
82
|
return join(registryDir(), "projects.json");
|
|
83
83
|
}
|
|
84
|
+
function emptyRegistry() {
|
|
85
|
+
return { projects: {} };
|
|
86
|
+
}
|
|
84
87
|
async function readRegistryRaw() {
|
|
85
88
|
try {
|
|
86
89
|
const content = await readFile(registryPath(), "utf-8");
|
|
@@ -88,9 +91,9 @@ async function readRegistryRaw() {
|
|
|
88
91
|
if (typeof parsed === "object" && parsed !== null && "projects" in parsed && typeof parsed.projects === "object" && parsed.projects !== null) {
|
|
89
92
|
return parsed;
|
|
90
93
|
}
|
|
91
|
-
return
|
|
94
|
+
return emptyRegistry();
|
|
92
95
|
} catch {
|
|
93
|
-
return
|
|
96
|
+
return emptyRegistry();
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
99
|
async function writeRegistry(registry) {
|
|
@@ -203,9 +206,8 @@ async function migrateFromLockfiles(searchDirs) {
|
|
|
203
206
|
}
|
|
204
207
|
return imported;
|
|
205
208
|
}
|
|
206
|
-
var _registryDirOverride,
|
|
209
|
+
var _registryDirOverride, SCAN_SKIP_DIRS;
|
|
207
210
|
var init_registry = __esm(() => {
|
|
208
|
-
EMPTY_REGISTRY = { projects: {} };
|
|
209
211
|
SCAN_SKIP_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
|
|
210
212
|
});
|
|
211
213
|
|
|
@@ -217,7 +219,7 @@ var init_package = __esm(() => {
|
|
|
217
219
|
workspaces: [
|
|
218
220
|
"packages/*"
|
|
219
221
|
],
|
|
220
|
-
version: "1.1.
|
|
222
|
+
version: "1.1.95",
|
|
221
223
|
description: "Batteries-included agent harness for Claude Code",
|
|
222
224
|
type: "module",
|
|
223
225
|
bin: {
|
|
@@ -7353,6 +7355,19 @@ async function writeJsonFile(path, data) {
|
|
|
7353
7355
|
const content = JSON.stringify(data, null, 2);
|
|
7354
7356
|
await fs.writeFile(path, content, "utf-8");
|
|
7355
7357
|
}
|
|
7358
|
+
function parseJsonText(raw) {
|
|
7359
|
+
const bom = raw.startsWith(UTF8_BOM);
|
|
7360
|
+
const text = bom ? raw.slice(UTF8_BOM.length) : raw;
|
|
7361
|
+
return {
|
|
7362
|
+
data: JSON.parse(text),
|
|
7363
|
+
format: { bom, trailingNewline: text.endsWith(`
|
|
7364
|
+
`) }
|
|
7365
|
+
};
|
|
7366
|
+
}
|
|
7367
|
+
function stringifyJson(data, format) {
|
|
7368
|
+
return `${format.bom ? UTF8_BOM : ""}${JSON.stringify(data, null, 2)}${format.trailingNewline ? `
|
|
7369
|
+
` : ""}`;
|
|
7370
|
+
}
|
|
7356
7371
|
async function readTextFile(path) {
|
|
7357
7372
|
const fs = await import("node:fs/promises");
|
|
7358
7373
|
return fs.readFile(path, "utf-8");
|
|
@@ -7408,6 +7423,7 @@ function matchesPattern(filename, pattern) {
|
|
|
7408
7423
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
7409
7424
|
return regex.test(filename);
|
|
7410
7425
|
}
|
|
7426
|
+
var UTF8_BOM = "\uFEFF";
|
|
7411
7427
|
var init_fs = () => {};
|
|
7412
7428
|
|
|
7413
7429
|
// src/cli/projects.ts
|
|
@@ -8220,7 +8236,7 @@ var init_theme = __esm(() => {
|
|
|
8220
8236
|
});
|
|
8221
8237
|
|
|
8222
8238
|
// node_modules/.bun/@inquirer+core@11.1.7+b219b5910764fa5c/node_modules/@inquirer/core/dist/lib/make-theme.js
|
|
8223
|
-
function
|
|
8239
|
+
function isPlainObject2(value) {
|
|
8224
8240
|
if (typeof value !== "object" || value === null)
|
|
8225
8241
|
return false;
|
|
8226
8242
|
let proto = value;
|
|
@@ -8234,7 +8250,7 @@ function deepMerge2(...objects) {
|
|
|
8234
8250
|
for (const obj of objects) {
|
|
8235
8251
|
for (const [key, value] of Object.entries(obj)) {
|
|
8236
8252
|
const prevValue = output[key];
|
|
8237
|
-
output[key] =
|
|
8253
|
+
output[key] = isPlainObject2(prevValue) && isPlainObject2(value) ? deepMerge2(prevValue, value) : value;
|
|
8238
8254
|
}
|
|
8239
8255
|
}
|
|
8240
8256
|
return output;
|
|
@@ -15616,7 +15632,7 @@ var MESSAGES = {
|
|
|
15616
15632
|
"install.entry_md_skipped": "{{entry}} skipped ({{reason}})",
|
|
15617
15633
|
"install.lockfile_generated": "Lockfile generated ({{files}} files tracked)",
|
|
15618
15634
|
"install.lockfile_failed": "Failed to generate lockfile: {{error}}",
|
|
15619
|
-
"install.hook_commands_migrated": "Migrated {{rewritten}} hook command(s) in settings.local.json",
|
|
15635
|
+
"install.hook_commands_migrated": "Migrated {{rewritten}} hook/statusLine command(s) in settings.local.json",
|
|
15620
15636
|
"install.hook_commands_migration_failed": "Failed to migrate hook commands in settings.local.json: {{error}}",
|
|
15621
15637
|
"lockfile.not_found": "Lockfile not found: {{path}}",
|
|
15622
15638
|
"lockfile.invalid_version": "Invalid lockfile version: {{path}}",
|
|
@@ -15639,15 +15655,86 @@ var MESSAGES = {
|
|
|
15639
15655
|
"update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
|
|
15640
15656
|
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} updated: {{hint}}",
|
|
15641
15657
|
"update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
|
|
15642
|
-
"update.hook_commands_migrated": "Migrated {{count}} hook command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
15658
|
+
"update.hook_commands_migrated": "Migrated {{count}} hook/statusLine command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
15643
15659
|
"update.hook_commands_migration_failed": "Failed to migrate hook commands in {{path}}",
|
|
15644
15660
|
"config.load_failed": "Failed to load config: {{error}}",
|
|
15645
15661
|
"config.not_found": "Config not found at {{path}}, using defaults",
|
|
15646
15662
|
"config.saved": "Config saved to {{path}}",
|
|
15647
15663
|
"config.deleted": "Config deleted from {{path}}",
|
|
15664
|
+
"config.invalid_preserve_path": "Ignored invalid preserve path: {{path}} ({{reason}})",
|
|
15665
|
+
"codex.already_installed": "Codex CLI is already installed",
|
|
15666
|
+
"codex.installing_brew": "Installing Codex CLI via Homebrew...",
|
|
15667
|
+
"codex.installing_npm": "Installing Codex CLI via npm...",
|
|
15668
|
+
"codex.install_failed": "Failed to install Codex CLI: {{error}}",
|
|
15669
|
+
"codex.unsupported_os": "Codex CLI auto-install is not supported on {{os}}",
|
|
15670
|
+
"rtk.already_installed": "RTK is already installed",
|
|
15671
|
+
"rtk.installing_brew": "Installing RTK via Homebrew...",
|
|
15672
|
+
"rtk.installing_curl": "Installing RTK via install script...",
|
|
15673
|
+
"rtk.install_failed": "Failed to install RTK: {{error}}",
|
|
15674
|
+
"rtk.unsupported_os": "RTK auto-install is not supported on {{os}}",
|
|
15675
|
+
"mcp.ontology_rag_configured": "ontology-rag MCP server configured successfully",
|
|
15676
|
+
"install.codex_installing": "Installing Codex CLI...",
|
|
15677
|
+
"install.codex_success": "Codex CLI installed successfully",
|
|
15678
|
+
"install.codex_already": "Codex CLI is already installed",
|
|
15679
|
+
"install.rtk_installing": "Installing RTK...",
|
|
15680
|
+
"install.rtk_success": "RTK installed successfully",
|
|
15681
|
+
"install.rtk_already": "RTK is already installed",
|
|
15682
|
+
"install.preserved": "Preserved {{files}} file(s) and {{dirs}} directory(ies) from the existing installation",
|
|
15683
|
+
"install.restored": "Restored {{files}} file(s) and {{dirs}} directory(ies) after installation",
|
|
15684
|
+
"install.backup_failed": "Failed to back up {{path}}: {{error}}",
|
|
15685
|
+
"install.hooks_settings_failed": "Failed to merge hooks into settings.local.json: {{error}}",
|
|
15686
|
+
"preserve.cleanup_failed": "Failed to clean up preservation directory {{dir}}: {{error}}",
|
|
15687
|
+
"preserve.extract_dir_failed": "Failed to preserve directory {{dir}}: {{error}}",
|
|
15688
|
+
"preserve.extract_failed": "Failed to preserve file {{file}}: {{error}}",
|
|
15689
|
+
"preserve.restore_dir_failed": "Failed to restore directory {{dir}}: {{error}}",
|
|
15690
|
+
"preserve.restore_failed": "Failed to restore file {{file}}: {{error}}",
|
|
15691
|
+
"preserve_files.invalid_path": "Ignored invalid preserve_files entry from {{source}}: {{path}} ({{reason}})",
|
|
15692
|
+
"update.codex_missing": "Codex CLI is not installed",
|
|
15693
|
+
"update.rtk_missing": "RTK is not installed",
|
|
15694
|
+
"update.self_update_skipped": "Skipped update: source project cannot update itself",
|
|
15695
|
+
"update.deprecated_file_invalid_path": "Skipped deprecated file with invalid path: {{path}} ({{reason}})",
|
|
15696
|
+
"update.deprecated_file_removed": "Removed deprecated file: {{path}} ({{reason}})",
|
|
15697
|
+
"update.entry_doc_created": "Entry document created: {{path}}",
|
|
15698
|
+
"update.entry_doc_force_updated": "Entry document force-updated: {{path}}",
|
|
15699
|
+
"update.entry_merge_warning": "Entry document merge warning: {{warning}}",
|
|
15700
|
+
"update.entry_template_not_found": "Entry document template not found: {{template}}",
|
|
15701
|
+
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}} overwritten: {{hint}}",
|
|
15702
|
+
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped: {{hint}}",
|
|
15703
|
+
"update.settings_local_backfill_failed": "Failed to backfill statusLine refreshInterval in {{path}}",
|
|
15648
15704
|
"general.done": "Done!",
|
|
15649
15705
|
"general.failed": "Failed",
|
|
15650
|
-
"general.skipped": "Skipped"
|
|
15706
|
+
"general.skipped": "Skipped",
|
|
15707
|
+
"install.agent_domain_excluded": "Excluded agent {{agent}} (domain: {{domain}})",
|
|
15708
|
+
"install.backed_up": "Backed up {{from}} to {{to}}",
|
|
15709
|
+
"install.hooks_settings_merged": "Merged hooks wiring into settings.local.json",
|
|
15710
|
+
"install.hooks_settings_skipped": "Skipped hooks wiring in settings.local.json: {{reason}}",
|
|
15711
|
+
"install.schemas_not_found": "schemas/tool-inputs.json template not found: {{path}}",
|
|
15712
|
+
"install.schemas_skipped": "Skipped schemas/tool-inputs.json: {{reason}}",
|
|
15713
|
+
"install.schemas_installed": "Installed schemas/tool-inputs.json",
|
|
15714
|
+
"install.settings_local_created": "Created settings.local.json",
|
|
15715
|
+
"install.settings_local_merged": "Merged statusLine into settings.local.json",
|
|
15716
|
+
"install.settings_local_refreshInterval_added": "Added statusLine refreshInterval to settings.local.json",
|
|
15717
|
+
"install.settings_local_skipped": "Skipped settings.local.json statusLine: {{reason}}",
|
|
15718
|
+
"install.settings_local_statusline_migrated": "Re-anchored default statusLine command in settings.local.json",
|
|
15719
|
+
"install.skill_scope_excluded": "Excluded skill {{skill}} (scope: {{scope}})",
|
|
15720
|
+
"install.statusline_installed": "Installed statusline.sh",
|
|
15721
|
+
"install.statusline_not_found": "statusline.sh template not found: {{path}}",
|
|
15722
|
+
"install.statusline_skipped": "Skipped statusline.sh: {{reason}}",
|
|
15723
|
+
"install.tests_config_installed": "Installed tests/tsconfig.json",
|
|
15724
|
+
"install.tests_config_not_found": "tests/tsconfig.json template not found: {{path}}",
|
|
15725
|
+
"install.tests_config_skipped": "Skipped tests/tsconfig.json: {{reason}}",
|
|
15726
|
+
"preserve.cleanup": "Cleaned up preservation directory: {{dir}}",
|
|
15727
|
+
"preserve.copied_json": "Copied JSON file: {{file}}",
|
|
15728
|
+
"preserve.extracted_dir": "Preserved directory: {{dir}}",
|
|
15729
|
+
"preserve.extracted_file": "Preserved file: {{file}}",
|
|
15730
|
+
"preserve.merged_json": "Merged JSON file: {{file}}",
|
|
15731
|
+
"preserve.restored_dir": "Restored directory: {{dir}}",
|
|
15732
|
+
"preserve.restored_file": "Restored file: {{file}}",
|
|
15733
|
+
"update.deprecated_files_cleaned": "Removed {{count}} deprecated file(s)",
|
|
15734
|
+
"update.entry_doc_merged": "Entry document merged: {{path}} ({{managed}} managed, {{custom}} custom sections)",
|
|
15735
|
+
"update.file_backed_up": "Backed up {{path}} to {{backup}}",
|
|
15736
|
+
"update.root_files_synced": "Root-level files synced: {{files}}",
|
|
15737
|
+
"update.settings_local_refreshInterval_backfilled": "Backfilled statusLine refreshInterval in settings.local.json"
|
|
15651
15738
|
},
|
|
15652
15739
|
ko: {
|
|
15653
15740
|
"install.start": "oh-my-customcode 초기화 중...",
|
|
@@ -15666,7 +15753,7 @@ var MESSAGES = {
|
|
|
15666
15753
|
"install.entry_md_skipped": "{{entry}} 건너뜀 ({{reason}})",
|
|
15667
15754
|
"install.lockfile_generated": "잠금 파일 생성 완료 ({{files}}개 파일 추적)",
|
|
15668
15755
|
"install.lockfile_failed": "잠금 파일 생성 실패: {{error}}",
|
|
15669
|
-
"install.hook_commands_migrated": "settings.local.json의
|
|
15756
|
+
"install.hook_commands_migrated": "settings.local.json의 훅/상태줄 명령 {{rewritten}}개를 마이그레이션했습니다",
|
|
15670
15757
|
"install.hook_commands_migration_failed": "settings.local.json의 훅 명령 마이그레이션에 실패했습니다: {{error}}",
|
|
15671
15758
|
"lockfile.not_found": "잠금 파일 없음: {{path}}",
|
|
15672
15759
|
"lockfile.invalid_version": "잠금 파일 버전 유효하지 않음: {{path}}",
|
|
@@ -15689,15 +15776,86 @@ var MESSAGES = {
|
|
|
15689
15776
|
"update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
|
|
15690
15777
|
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트됨: {{hint}}",
|
|
15691
15778
|
"update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
|
|
15692
|
-
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로
|
|
15779
|
+
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로 훅/상태줄 명령 {{count}}개를 마이그레이션했습니다",
|
|
15693
15780
|
"update.hook_commands_migration_failed": "{{path}}의 훅 명령 마이그레이션에 실패했습니다",
|
|
15694
15781
|
"config.load_failed": "설정 로드 실패: {{error}}",
|
|
15695
15782
|
"config.not_found": "{{path}}에 설정 없음, 기본값 사용",
|
|
15696
15783
|
"config.saved": "설정 저장: {{path}}",
|
|
15697
15784
|
"config.deleted": "설정 삭제: {{path}}",
|
|
15785
|
+
"config.invalid_preserve_path": "유효하지 않은 보존 경로를 무시했습니다: {{path}} ({{reason}})",
|
|
15786
|
+
"codex.already_installed": "Codex CLI가 이미 설치되어 있습니다",
|
|
15787
|
+
"codex.installing_brew": "Homebrew로 Codex CLI를 설치합니다...",
|
|
15788
|
+
"codex.installing_npm": "npm으로 Codex CLI를 설치합니다...",
|
|
15789
|
+
"codex.install_failed": "Codex CLI 설치에 실패했습니다: {{error}}",
|
|
15790
|
+
"codex.unsupported_os": "{{os}}에서는 Codex CLI 자동 설치를 지원하지 않습니다",
|
|
15791
|
+
"rtk.already_installed": "RTK가 이미 설치되어 있습니다",
|
|
15792
|
+
"rtk.installing_brew": "Homebrew로 RTK를 설치합니다...",
|
|
15793
|
+
"rtk.installing_curl": "설치 스크립트로 RTK를 설치합니다...",
|
|
15794
|
+
"rtk.install_failed": "RTK 설치에 실패했습니다: {{error}}",
|
|
15795
|
+
"rtk.unsupported_os": "{{os}}에서는 RTK 자동 설치를 지원하지 않습니다",
|
|
15796
|
+
"mcp.ontology_rag_configured": "ontology-rag MCP 서버 구성을 완료했습니다",
|
|
15797
|
+
"install.codex_installing": "Codex CLI를 설치합니다...",
|
|
15798
|
+
"install.codex_success": "Codex CLI를 설치했습니다",
|
|
15799
|
+
"install.codex_already": "Codex CLI가 이미 설치되어 있습니다",
|
|
15800
|
+
"install.rtk_installing": "RTK를 설치합니다...",
|
|
15801
|
+
"install.rtk_success": "RTK를 설치했습니다",
|
|
15802
|
+
"install.rtk_already": "RTK가 이미 설치되어 있습니다",
|
|
15803
|
+
"install.preserved": "기존 설치에서 파일 {{files}}개와 디렉토리 {{dirs}}개를 보존했습니다",
|
|
15804
|
+
"install.restored": "설치 후 파일 {{files}}개와 디렉토리 {{dirs}}개를 복원했습니다",
|
|
15805
|
+
"install.backup_failed": "{{path}} 백업에 실패했습니다: {{error}}",
|
|
15806
|
+
"install.hooks_settings_failed": "settings.local.json에 훅을 병합하지 못했습니다: {{error}}",
|
|
15807
|
+
"preserve.cleanup_failed": "보존 디렉토리 {{dir}} 정리에 실패했습니다: {{error}}",
|
|
15808
|
+
"preserve.extract_dir_failed": "디렉토리 {{dir}} 보존에 실패했습니다: {{error}}",
|
|
15809
|
+
"preserve.extract_failed": "파일 {{file}} 보존에 실패했습니다: {{error}}",
|
|
15810
|
+
"preserve.restore_dir_failed": "디렉토리 {{dir}} 복원에 실패했습니다: {{error}}",
|
|
15811
|
+
"preserve.restore_failed": "파일 {{file}} 복원에 실패했습니다: {{error}}",
|
|
15812
|
+
"preserve_files.invalid_path": "{{source}}의 유효하지 않은 preserve_files 항목을 무시했습니다: {{path}} ({{reason}})",
|
|
15813
|
+
"update.codex_missing": "Codex CLI가 설치되어 있지 않습니다",
|
|
15814
|
+
"update.rtk_missing": "RTK가 설치되어 있지 않습니다",
|
|
15815
|
+
"update.self_update_skipped": "업데이트를 건너뜁니다: 소스 프로젝트는 스스로 업데이트할 수 없습니다",
|
|
15816
|
+
"update.deprecated_file_invalid_path": "경로가 유효하지 않아 지원 중단 파일을 건너뜁니다: {{path}} ({{reason}})",
|
|
15817
|
+
"update.deprecated_file_removed": "지원 중단 파일을 삭제했습니다: {{path}} ({{reason}})",
|
|
15818
|
+
"update.entry_doc_created": "진입 문서를 생성했습니다: {{path}}",
|
|
15819
|
+
"update.entry_doc_force_updated": "진입 문서를 강제로 업데이트했습니다: {{path}}",
|
|
15820
|
+
"update.entry_merge_warning": "진입 문서 병합 경고: {{warning}}",
|
|
15821
|
+
"update.entry_template_not_found": "진입 문서 템플릿을 찾을 수 없습니다: {{template}}",
|
|
15822
|
+
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}}을(를) 덮어썼습니다: {{hint}}",
|
|
15823
|
+
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다: {{hint}}",
|
|
15824
|
+
"update.settings_local_backfill_failed": "{{path}}의 statusLine refreshInterval 보충에 실패했습니다",
|
|
15698
15825
|
"general.done": "완료!",
|
|
15699
15826
|
"general.failed": "실패",
|
|
15700
|
-
"general.skipped": "건너뜀"
|
|
15827
|
+
"general.skipped": "건너뜀",
|
|
15828
|
+
"install.agent_domain_excluded": "에이전트 {{agent}}을(를) 제외했습니다 (도메인: {{domain}})",
|
|
15829
|
+
"install.backed_up": "{{from}}을(를) {{to}}(으)로 백업했습니다",
|
|
15830
|
+
"install.hooks_settings_merged": "settings.local.json에 훅 연결을 병합했습니다",
|
|
15831
|
+
"install.hooks_settings_skipped": "settings.local.json의 훅 연결을 건너뜁니다: {{reason}}",
|
|
15832
|
+
"install.schemas_not_found": "schemas/tool-inputs.json 템플릿을 찾을 수 없습니다: {{path}}",
|
|
15833
|
+
"install.schemas_skipped": "schemas/tool-inputs.json 설치를 건너뜁니다: {{reason}}",
|
|
15834
|
+
"install.schemas_installed": "schemas/tool-inputs.json을 설치했습니다",
|
|
15835
|
+
"install.settings_local_created": "settings.local.json을 생성했습니다",
|
|
15836
|
+
"install.settings_local_merged": "settings.local.json에 statusLine을 병합했습니다",
|
|
15837
|
+
"install.settings_local_refreshInterval_added": "settings.local.json에 statusLine refreshInterval을 추가했습니다",
|
|
15838
|
+
"install.settings_local_skipped": "settings.local.json의 statusLine을 건너뜁니다: {{reason}}",
|
|
15839
|
+
"install.settings_local_statusline_migrated": "settings.local.json의 기본 statusLine 명령을 프로젝트 루트 기준으로 변경했습니다",
|
|
15840
|
+
"install.skill_scope_excluded": "스킬 {{skill}}을(를) 제외했습니다 (범위: {{scope}})",
|
|
15841
|
+
"install.statusline_installed": "statusline.sh를 설치했습니다",
|
|
15842
|
+
"install.statusline_not_found": "statusline.sh 템플릿을 찾을 수 없습니다: {{path}}",
|
|
15843
|
+
"install.statusline_skipped": "statusline.sh 설치를 건너뜁니다: {{reason}}",
|
|
15844
|
+
"install.tests_config_installed": "tests/tsconfig.json을 설치했습니다",
|
|
15845
|
+
"install.tests_config_not_found": "tests/tsconfig.json 템플릿을 찾을 수 없습니다: {{path}}",
|
|
15846
|
+
"install.tests_config_skipped": "tests/tsconfig.json 설치를 건너뜁니다: {{reason}}",
|
|
15847
|
+
"preserve.cleanup": "보존 디렉토리를 정리했습니다: {{dir}}",
|
|
15848
|
+
"preserve.copied_json": "JSON 파일을 복사했습니다: {{file}}",
|
|
15849
|
+
"preserve.extracted_dir": "디렉토리를 보존했습니다: {{dir}}",
|
|
15850
|
+
"preserve.extracted_file": "파일을 보존했습니다: {{file}}",
|
|
15851
|
+
"preserve.merged_json": "JSON 파일을 병합했습니다: {{file}}",
|
|
15852
|
+
"preserve.restored_dir": "디렉토리를 복원했습니다: {{dir}}",
|
|
15853
|
+
"preserve.restored_file": "파일을 복원했습니다: {{file}}",
|
|
15854
|
+
"update.deprecated_files_cleaned": "지원 중단 파일 {{count}}개를 삭제했습니다",
|
|
15855
|
+
"update.entry_doc_merged": "진입 문서를 병합했습니다: {{path}} (관리 섹션 {{managed}}개, 사용자 섹션 {{custom}}개)",
|
|
15856
|
+
"update.file_backed_up": "{{path}}을(를) {{backup}}(으)로 백업했습니다",
|
|
15857
|
+
"update.root_files_synced": "루트 수준 파일을 동기화했습니다: {{files}}",
|
|
15858
|
+
"update.settings_local_refreshInterval_backfilled": "settings.local.json의 statusLine refreshInterval을 보충했습니다"
|
|
15701
15859
|
}
|
|
15702
15860
|
};
|
|
15703
15861
|
function getMessage(key, params) {
|
|
@@ -16961,11 +17119,122 @@ import {
|
|
|
16961
17119
|
rename,
|
|
16962
17120
|
stat as stat2
|
|
16963
17121
|
} from "node:fs/promises";
|
|
16964
|
-
import { basename as basename3, join as join8 } from "node:path";
|
|
17122
|
+
import { basename as basename3, dirname as dirname4, join as join8 } from "node:path";
|
|
16965
17123
|
|
|
16966
17124
|
// src/core/file-preservation.ts
|
|
16967
17125
|
init_fs();
|
|
16968
17126
|
import { basename as basename2, join as join7 } from "node:path";
|
|
17127
|
+
|
|
17128
|
+
// src/core/hook-group-merge.ts
|
|
17129
|
+
var LEGACY_OMCUSTOM_DESCRIPTIONS = [
|
|
17130
|
+
"Record agent outcomes + auto-continue workflow + R007/R008 drift advisory on subagent completion (autonomous-loop re-entry, #1545)",
|
|
17131
|
+
'Re-inject project CLAUDE.md into model context on session start/resume/clear and compact re-entry — matcher "*" covers all SessionStart sources including "compact" (#1617)'
|
|
17132
|
+
];
|
|
17133
|
+
var STANDALONE_SCRIPT_COMMAND = /^(?:bash )?(?:"(?:\$\{CLAUDE_PROJECT_DIR:-\.\}\/|\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)"|(?:\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh))$/;
|
|
17134
|
+
var SCRIPT_REFERENCE = /\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)/g;
|
|
17135
|
+
function matcherKey(group) {
|
|
17136
|
+
return typeof group.matcher === "string" ? group.matcher : "";
|
|
17137
|
+
}
|
|
17138
|
+
function isRecord(value) {
|
|
17139
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17140
|
+
}
|
|
17141
|
+
function addHookOwnership(hook, owned) {
|
|
17142
|
+
if (!isRecord(hook) || typeof hook.command !== "string") {
|
|
17143
|
+
return;
|
|
17144
|
+
}
|
|
17145
|
+
owned.commands.add(hook.command);
|
|
17146
|
+
for (const match of hook.command.matchAll(SCRIPT_REFERENCE)) {
|
|
17147
|
+
owned.scripts.add(match[1]);
|
|
17148
|
+
}
|
|
17149
|
+
}
|
|
17150
|
+
function addGroupOwnership(group, owned, descriptions) {
|
|
17151
|
+
if (!isRecord(group)) {
|
|
17152
|
+
return;
|
|
17153
|
+
}
|
|
17154
|
+
const key = matcherKey(group);
|
|
17155
|
+
let forMatcher = owned.get(key);
|
|
17156
|
+
if (forMatcher === undefined) {
|
|
17157
|
+
forMatcher = { commands: new Set, scripts: new Set };
|
|
17158
|
+
owned.set(key, forMatcher);
|
|
17159
|
+
}
|
|
17160
|
+
if (typeof group.description === "string") {
|
|
17161
|
+
descriptions.add(group.description);
|
|
17162
|
+
}
|
|
17163
|
+
if (Array.isArray(group.hooks)) {
|
|
17164
|
+
for (const hook of group.hooks) {
|
|
17165
|
+
addHookOwnership(hook, forMatcher);
|
|
17166
|
+
}
|
|
17167
|
+
}
|
|
17168
|
+
}
|
|
17169
|
+
function collectOwnership(generated) {
|
|
17170
|
+
const descriptions = new Set(LEGACY_OMCUSTOM_DESCRIPTIONS);
|
|
17171
|
+
const events = new Map;
|
|
17172
|
+
for (const [event, groups] of Object.entries(generated)) {
|
|
17173
|
+
const owned = new Map;
|
|
17174
|
+
events.set(event, owned);
|
|
17175
|
+
for (const group of groups) {
|
|
17176
|
+
addGroupOwnership(group, owned, descriptions);
|
|
17177
|
+
}
|
|
17178
|
+
}
|
|
17179
|
+
return { descriptions, events };
|
|
17180
|
+
}
|
|
17181
|
+
function isOwnedCommand(hook, owned) {
|
|
17182
|
+
if (owned === undefined || !isRecord(hook) || hook.type !== "command") {
|
|
17183
|
+
return false;
|
|
17184
|
+
}
|
|
17185
|
+
const command = hook.command;
|
|
17186
|
+
if (typeof command !== "string") {
|
|
17187
|
+
return false;
|
|
17188
|
+
}
|
|
17189
|
+
if (owned.commands.has(command)) {
|
|
17190
|
+
return true;
|
|
17191
|
+
}
|
|
17192
|
+
const match = STANDALONE_SCRIPT_COMMAND.exec(command);
|
|
17193
|
+
const script = match?.[1] ?? match?.[2];
|
|
17194
|
+
return script !== undefined && owned.scripts.has(script);
|
|
17195
|
+
}
|
|
17196
|
+
function userResidual(group, owned, ownership) {
|
|
17197
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
17198
|
+
return [group];
|
|
17199
|
+
}
|
|
17200
|
+
if (typeof group.description === "string" && ownership.descriptions.has(group.description)) {
|
|
17201
|
+
return [];
|
|
17202
|
+
}
|
|
17203
|
+
const ownedForMatcher = owned?.get(matcherKey(group));
|
|
17204
|
+
const remaining = group.hooks.filter((hook) => !isOwnedCommand(hook, ownedForMatcher));
|
|
17205
|
+
if (remaining.length === group.hooks.length) {
|
|
17206
|
+
return [group];
|
|
17207
|
+
}
|
|
17208
|
+
return remaining.length === 0 ? [] : [{ ...group, hooks: remaining }];
|
|
17209
|
+
}
|
|
17210
|
+
function mergeHookBlocks(existing, generated) {
|
|
17211
|
+
const result = {};
|
|
17212
|
+
for (const [event, groups] of Object.entries(generated)) {
|
|
17213
|
+
result[event] = [...groups];
|
|
17214
|
+
}
|
|
17215
|
+
if (!isRecord(existing)) {
|
|
17216
|
+
return result;
|
|
17217
|
+
}
|
|
17218
|
+
const ownership = collectOwnership(generated);
|
|
17219
|
+
for (const [event, value] of Object.entries(existing)) {
|
|
17220
|
+
if (!Array.isArray(value)) {
|
|
17221
|
+
if (!ownership.events.has(event)) {
|
|
17222
|
+
result[event] = value;
|
|
17223
|
+
}
|
|
17224
|
+
continue;
|
|
17225
|
+
}
|
|
17226
|
+
const owned = ownership.events.get(event);
|
|
17227
|
+
const residual = value.flatMap((group) => userResidual(group, owned, ownership));
|
|
17228
|
+
if (residual.length === 0) {
|
|
17229
|
+
continue;
|
|
17230
|
+
}
|
|
17231
|
+
const current = result[event];
|
|
17232
|
+
result[event] = Array.isArray(current) ? [...current, ...residual] : residual;
|
|
17233
|
+
}
|
|
17234
|
+
return result;
|
|
17235
|
+
}
|
|
17236
|
+
|
|
17237
|
+
// src/core/file-preservation.ts
|
|
16969
17238
|
var DEFAULT_CRITICAL_FILES = ["settings.json", "settings.local.json"];
|
|
16970
17239
|
var DEFAULT_CRITICAL_DIRECTORIES = ["agent-memory", "agent-memory-local"];
|
|
16971
17240
|
var PROTECTED_FRAMEWORK_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
@@ -17075,12 +17344,19 @@ async function restoreCriticalFiles(rootDir, preservation) {
|
|
|
17075
17344
|
}
|
|
17076
17345
|
return result;
|
|
17077
17346
|
}
|
|
17347
|
+
var HOOKS_MERGE_FILES = new Set(["settings.json", "settings.local.json"]);
|
|
17348
|
+
function isGeneratedHookBlock(value) {
|
|
17349
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((groups) => Array.isArray(groups));
|
|
17350
|
+
}
|
|
17078
17351
|
async function mergeJsonFile(preservedPath, targetPath) {
|
|
17079
|
-
const preservedData = await
|
|
17352
|
+
const { data: preservedData, format } = parseJsonText(await readTextFile(preservedPath));
|
|
17080
17353
|
if (await fileExists(targetPath)) {
|
|
17081
|
-
const targetData = await
|
|
17354
|
+
const { data: targetData } = parseJsonText(await readTextFile(targetPath));
|
|
17082
17355
|
const merged = deepMerge(targetData, preservedData);
|
|
17083
|
-
|
|
17356
|
+
if (HOOKS_MERGE_FILES.has(basename2(targetPath)) && isGeneratedHookBlock(targetData.hooks)) {
|
|
17357
|
+
merged.hooks = mergeHookBlocks(preservedData.hooks, targetData.hooks);
|
|
17358
|
+
}
|
|
17359
|
+
await writeTextFile(targetPath, stringifyJson(merged, format));
|
|
17084
17360
|
debug("preserve.merged_json", { file: basename2(targetPath) });
|
|
17085
17361
|
} else {
|
|
17086
17362
|
await copyFile(preservedPath, targetPath);
|
|
@@ -17368,14 +17644,14 @@ function rewriteRelativeHookCommand(command) {
|
|
|
17368
17644
|
const interpreter = match[1] ?? "";
|
|
17369
17645
|
return `${interpreter}"\${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/${match[2]}"`;
|
|
17370
17646
|
}
|
|
17371
|
-
function
|
|
17647
|
+
function isRecord2(value) {
|
|
17372
17648
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17373
17649
|
}
|
|
17374
17650
|
function rewriteNode(node, counter) {
|
|
17375
17651
|
if (Array.isArray(node)) {
|
|
17376
17652
|
return node.map((item) => rewriteNode(item, counter));
|
|
17377
17653
|
}
|
|
17378
|
-
if (!
|
|
17654
|
+
if (!isRecord2(node)) {
|
|
17379
17655
|
return node;
|
|
17380
17656
|
}
|
|
17381
17657
|
const out = {};
|
|
@@ -17392,16 +17668,32 @@ function rewriteNode(node, counter) {
|
|
|
17392
17668
|
}
|
|
17393
17669
|
return out;
|
|
17394
17670
|
}
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
|
|
17671
|
+
var OLD_STATUSLINE_COMMAND = ".claude/statusline.sh";
|
|
17672
|
+
var NEW_STATUSLINE_COMMAND = `bash "\${CLAUDE_PROJECT_DIR:-.}/.claude/statusline.sh"`;
|
|
17673
|
+
function migrateStatusLine(statusLine) {
|
|
17674
|
+
if (!isRecord2(statusLine) || statusLine.command !== OLD_STATUSLINE_COMMAND) {
|
|
17675
|
+
return null;
|
|
17398
17676
|
}
|
|
17677
|
+
return { ...statusLine, command: NEW_STATUSLINE_COMMAND };
|
|
17678
|
+
}
|
|
17679
|
+
function migrateHookCommands(settings) {
|
|
17399
17680
|
const counter = { count: 0 };
|
|
17400
|
-
const
|
|
17681
|
+
const updates = {};
|
|
17682
|
+
if (isRecord2(settings.hooks)) {
|
|
17683
|
+
const hooks = rewriteNode(settings.hooks, counter);
|
|
17684
|
+
if (counter.count > 0) {
|
|
17685
|
+
updates.hooks = hooks;
|
|
17686
|
+
}
|
|
17687
|
+
}
|
|
17688
|
+
const statusLine = migrateStatusLine(settings.statusLine);
|
|
17689
|
+
if (statusLine !== null) {
|
|
17690
|
+
counter.count++;
|
|
17691
|
+
updates.statusLine = statusLine;
|
|
17692
|
+
}
|
|
17401
17693
|
if (counter.count === 0) {
|
|
17402
17694
|
return { settings, rewritten: 0 };
|
|
17403
17695
|
}
|
|
17404
|
-
return { settings: { ...settings,
|
|
17696
|
+
return { settings: { ...settings, ...updates }, rewritten: counter.count };
|
|
17405
17697
|
}
|
|
17406
17698
|
|
|
17407
17699
|
// src/core/hooks-settings.ts
|
|
@@ -17750,7 +18042,7 @@ var CLASSIFICATION_TABLE = [
|
|
|
17750
18042
|
event: "PreToolUse",
|
|
17751
18043
|
description: "Block rule file deletion — requires individual user confirmation per rule (R001 safety)",
|
|
17752
18044
|
decision: "drop-condition",
|
|
17753
|
-
reason: 'rule-deletion-guard.sh explicitly re-checks `tool != "Bash"` (passthrough) and
|
|
18045
|
+
reason: 'rule-deletion-guard.sh explicitly re-checks `tool != "Bash"` (passthrough) and itself decides whether to block: it blocks destructive operations (rm/mv/unlink/find -delete/xargs rm/truncate/git rm|mv|clean/`>` redirect) whose resolved target is inside or an ancestor of the project .claude/rules or templates/.claude/rules, falling back to pattern matching for opaque commands (use the Write/Edit tools to modify rule files) — matcher condition is redundant'
|
|
17754
18046
|
},
|
|
17755
18047
|
{
|
|
17756
18048
|
event: "PostToolUse",
|
|
@@ -17874,20 +18166,44 @@ function convertHooksJson(hooksJson) {
|
|
|
17874
18166
|
}
|
|
17875
18167
|
return { hooks: result, warnings };
|
|
17876
18168
|
}
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
18169
|
+
var UTF8_BOM2 = "\uFEFF";
|
|
18170
|
+
function isPlainObject(value) {
|
|
18171
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18172
|
+
}
|
|
18173
|
+
async function readExistingSettings(settingsPath) {
|
|
18174
|
+
let raw;
|
|
17881
18175
|
try {
|
|
17882
|
-
|
|
17883
|
-
settings = JSON.parse(existingRaw);
|
|
18176
|
+
raw = await readTextFile(settingsPath);
|
|
17884
18177
|
} catch {
|
|
17885
|
-
settings
|
|
18178
|
+
return { ok: true, settings: {}, bom: false };
|
|
18179
|
+
}
|
|
18180
|
+
const bom = raw.startsWith(UTF8_BOM2);
|
|
18181
|
+
if ((bom ? raw.slice(UTF8_BOM2.length) : raw).trim() === "") {
|
|
18182
|
+
return { ok: true, settings: {}, bom };
|
|
18183
|
+
}
|
|
18184
|
+
try {
|
|
18185
|
+
const { data } = parseJsonText(raw);
|
|
18186
|
+
if (isPlainObject(data)) {
|
|
18187
|
+
return { ok: true, settings: data, bom };
|
|
18188
|
+
}
|
|
18189
|
+
return { ok: false, reason: "the top-level JSON value is not a JSON object", bom };
|
|
18190
|
+
} catch (err) {
|
|
18191
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err), bom };
|
|
17886
18192
|
}
|
|
17887
|
-
|
|
18193
|
+
}
|
|
18194
|
+
async function mergeHooksIntoSettings(settingsPath, hooksJsonPath, options = {}) {
|
|
18195
|
+
const hooksJson = await readJsonFile(hooksJsonPath);
|
|
18196
|
+
const { hooks, warnings } = convertHooksJson(hooksJson);
|
|
18197
|
+
const preserve = options.preserveUserHooks === true;
|
|
18198
|
+
const existing = await readExistingSettings(settingsPath);
|
|
18199
|
+
if (!existing.ok && preserve) {
|
|
18200
|
+
warnings.push(`hooks: could not parse existing ${settingsPath} (${existing.reason}); the file was left unchanged and hooks were NOT installed — fix or move the file, then re-run \`omcustom init\``);
|
|
18201
|
+
return { warnings };
|
|
18202
|
+
}
|
|
18203
|
+
const settings = existing.ok ? existing.settings : {};
|
|
18204
|
+
settings.hooks = preserve ? mergeHookBlocks(settings.hooks, hooks) : hooks;
|
|
17888
18205
|
await ensureDirectory(dirname3(settingsPath));
|
|
17889
|
-
await writeTextFile(settingsPath,
|
|
17890
|
-
`);
|
|
18206
|
+
await writeTextFile(settingsPath, stringifyJson(settings, { bom: existing.bom, trailingNewline: true }));
|
|
17891
18207
|
return { warnings };
|
|
17892
18208
|
}
|
|
17893
18209
|
|
|
@@ -18019,6 +18335,25 @@ async function installStatusline(targetDir, options, _result) {
|
|
|
18019
18335
|
await fs.chmod(destPath, 493);
|
|
18020
18336
|
debug("install.statusline_installed", {});
|
|
18021
18337
|
}
|
|
18338
|
+
async function installSchemas(targetDir, options, _result) {
|
|
18339
|
+
const layout = getProviderLayout();
|
|
18340
|
+
const relPath = join8(layout.rootDir, "schemas", "tool-inputs.json");
|
|
18341
|
+
const srcPath = resolveTemplatePath(relPath);
|
|
18342
|
+
const destPath = join8(targetDir, relPath);
|
|
18343
|
+
if (!await fileExists(srcPath)) {
|
|
18344
|
+
debug("install.schemas_not_found", { path: srcPath });
|
|
18345
|
+
return;
|
|
18346
|
+
}
|
|
18347
|
+
if (await fileExists(destPath)) {
|
|
18348
|
+
if (!options.force && !options.backup) {
|
|
18349
|
+
debug("install.schemas_skipped", { reason: "exists" });
|
|
18350
|
+
return;
|
|
18351
|
+
}
|
|
18352
|
+
}
|
|
18353
|
+
await ensureDirectory(dirname4(destPath));
|
|
18354
|
+
await copyFile(srcPath, destPath);
|
|
18355
|
+
debug("install.schemas_installed", {});
|
|
18356
|
+
}
|
|
18022
18357
|
async function installTestsConfig(targetDir, options, _result) {
|
|
18023
18358
|
const srcPath = resolveTemplatePath(join8("tests", "tsconfig.json"));
|
|
18024
18359
|
const destPath = join8(targetDir, "tests", "tsconfig.json");
|
|
@@ -18035,34 +18370,55 @@ async function installTestsConfig(targetDir, options, _result) {
|
|
|
18035
18370
|
await copyFile(srcPath, destPath);
|
|
18036
18371
|
debug("install.tests_config_installed", {});
|
|
18037
18372
|
}
|
|
18373
|
+
var STATUSLINE_REFRESH_INTERVAL = 10;
|
|
18374
|
+
function updateExistingStatusLine(existing) {
|
|
18375
|
+
const migrated = migrateHookCommands({ statusLine: existing.statusLine });
|
|
18376
|
+
const commandMigrated = migrated.rewritten > 0;
|
|
18377
|
+
if (commandMigrated) {
|
|
18378
|
+
existing.statusLine = migrated.settings.statusLine;
|
|
18379
|
+
}
|
|
18380
|
+
const sl = existing.statusLine;
|
|
18381
|
+
const refreshIntervalAdded = sl.refreshInterval === undefined;
|
|
18382
|
+
if (refreshIntervalAdded) {
|
|
18383
|
+
sl.refreshInterval = STATUSLINE_REFRESH_INTERVAL;
|
|
18384
|
+
}
|
|
18385
|
+
return { commandMigrated, refreshIntervalAdded };
|
|
18386
|
+
}
|
|
18387
|
+
async function mergeStatusLineIntoExisting(settingsPath, existing, format, defaults) {
|
|
18388
|
+
if (!existing.statusLine) {
|
|
18389
|
+
existing.statusLine = defaults.statusLine;
|
|
18390
|
+
await writeTextFile(settingsPath, stringifyJson(existing, format));
|
|
18391
|
+
debug("install.settings_local_merged", {});
|
|
18392
|
+
return;
|
|
18393
|
+
}
|
|
18394
|
+
const { commandMigrated, refreshIntervalAdded } = updateExistingStatusLine(existing);
|
|
18395
|
+
if (!commandMigrated && !refreshIntervalAdded) {
|
|
18396
|
+
debug("install.settings_local_skipped", { reason: "statusLine exists" });
|
|
18397
|
+
return;
|
|
18398
|
+
}
|
|
18399
|
+
await writeTextFile(settingsPath, stringifyJson(existing, format));
|
|
18400
|
+
if (commandMigrated) {
|
|
18401
|
+
debug("install.settings_local_statusline_migrated", {});
|
|
18402
|
+
}
|
|
18403
|
+
if (refreshIntervalAdded) {
|
|
18404
|
+
debug("install.settings_local_refreshInterval_added", {});
|
|
18405
|
+
}
|
|
18406
|
+
}
|
|
18038
18407
|
async function installSettingsLocal(targetDir, result) {
|
|
18039
18408
|
const layout = getProviderLayout();
|
|
18040
18409
|
const settingsPath = join8(targetDir, layout.rootDir, "settings.local.json");
|
|
18041
18410
|
const statusLineConfig = {
|
|
18042
18411
|
statusLine: {
|
|
18043
18412
|
type: "command",
|
|
18044
|
-
command:
|
|
18413
|
+
command: NEW_STATUSLINE_COMMAND,
|
|
18045
18414
|
padding: 0,
|
|
18046
|
-
refreshInterval:
|
|
18415
|
+
refreshInterval: STATUSLINE_REFRESH_INTERVAL
|
|
18047
18416
|
}
|
|
18048
18417
|
};
|
|
18049
18418
|
if (await fileExists(settingsPath)) {
|
|
18050
18419
|
try {
|
|
18051
|
-
const existing = await
|
|
18052
|
-
|
|
18053
|
-
existing.statusLine = statusLineConfig.statusLine;
|
|
18054
|
-
await writeJsonFile(settingsPath, existing);
|
|
18055
|
-
debug("install.settings_local_merged", {});
|
|
18056
|
-
} else {
|
|
18057
|
-
const sl = existing.statusLine;
|
|
18058
|
-
if (sl.refreshInterval === undefined) {
|
|
18059
|
-
sl.refreshInterval = statusLineConfig.statusLine.refreshInterval;
|
|
18060
|
-
await writeJsonFile(settingsPath, existing);
|
|
18061
|
-
debug("install.settings_local_refreshInterval_added", {});
|
|
18062
|
-
} else {
|
|
18063
|
-
debug("install.settings_local_skipped", { reason: "statusLine exists" });
|
|
18064
|
-
}
|
|
18065
|
-
}
|
|
18420
|
+
const { data: existing, format } = parseJsonText(await readTextFile(settingsPath));
|
|
18421
|
+
await mergeStatusLineIntoExisting(settingsPath, existing, format, statusLineConfig);
|
|
18066
18422
|
} catch {
|
|
18067
18423
|
result.warnings.push("Failed to parse existing settings.local.json, skipping statusLine config");
|
|
18068
18424
|
}
|
|
@@ -18080,7 +18436,9 @@ async function installHooksSettings(targetDir, _options, result) {
|
|
|
18080
18436
|
return;
|
|
18081
18437
|
}
|
|
18082
18438
|
try {
|
|
18083
|
-
const mergeResult = await mergeHooksIntoSettings(settingsPath, hooksJsonPath
|
|
18439
|
+
const mergeResult = await mergeHooksIntoSettings(settingsPath, hooksJsonPath, {
|
|
18440
|
+
preserveUserHooks: true
|
|
18441
|
+
});
|
|
18084
18442
|
if (mergeResult && Array.isArray(mergeResult.warnings) && mergeResult.warnings.length > 0) {
|
|
18085
18443
|
result.warnings.push(...mergeResult.warnings);
|
|
18086
18444
|
}
|
|
@@ -18173,6 +18531,7 @@ async function install(options) {
|
|
|
18173
18531
|
await verifyTemplateDirectory();
|
|
18174
18532
|
await installAllComponents(options.targetDir, options, result);
|
|
18175
18533
|
await installStatusline(options.targetDir, options, result);
|
|
18534
|
+
await installSchemas(options.targetDir, options, result);
|
|
18176
18535
|
await installTestsConfig(options.targetDir, options, result);
|
|
18177
18536
|
await installSettingsLocal(options.targetDir, result);
|
|
18178
18537
|
await installHooksSettings(options.targetDir, options, result);
|
|
@@ -18430,7 +18789,7 @@ async function generateMCPConfig(targetDir) {
|
|
|
18430
18789
|
await writeFile2(mcpConfigPath, `${JSON.stringify(config, null, 2)}
|
|
18431
18790
|
`);
|
|
18432
18791
|
}
|
|
18433
|
-
info("
|
|
18792
|
+
info("mcp.ontology_rag_configured");
|
|
18434
18793
|
}
|
|
18435
18794
|
|
|
18436
18795
|
// src/core/ontology-rag-setup.ts
|
|
@@ -19660,7 +20019,7 @@ async function initCommand(options) {
|
|
|
19660
20019
|
}
|
|
19661
20020
|
|
|
19662
20021
|
// src/cli/list.ts
|
|
19663
|
-
import { basename as basename5, dirname as
|
|
20022
|
+
import { basename as basename5, dirname as dirname5, join as join14, relative as relative3 } from "node:path";
|
|
19664
20023
|
init_fs();
|
|
19665
20024
|
var ALLOWED_TOP_LEVEL_KEYS = new Set(["name", "type", "description", "version", "category"]);
|
|
19666
20025
|
function parseKeyValue(line) {
|
|
@@ -19861,7 +20220,7 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
|
|
|
19861
20220
|
const customSkillPaths = new Set(customComponents.filter((c) => c.type === "skill").map((c) => c.path));
|
|
19862
20221
|
const skillMdFiles = await listFiles(skillsDir, { recursive: true, pattern: "SKILL.md" });
|
|
19863
20222
|
const skills = await Promise.all(skillMdFiles.map(async (skillMdPath) => {
|
|
19864
|
-
const skillDir =
|
|
20223
|
+
const skillDir = dirname5(skillMdPath);
|
|
19865
20224
|
const indexYamlPath = join14(skillDir, "index.yaml");
|
|
19866
20225
|
const { description, version } = await tryReadIndexYamlMetadata(indexYamlPath);
|
|
19867
20226
|
const relativePath = relative3(targetDir, skillDir);
|
|
@@ -21061,11 +21420,12 @@ async function migrateHookCommandsInSettingsLocal(targetDir, options) {
|
|
|
21061
21420
|
});
|
|
21062
21421
|
}
|
|
21063
21422
|
}
|
|
21064
|
-
async function runFullUpdatePostProcessing(options, result, config) {
|
|
21423
|
+
async function runFullUpdatePostProcessing(options, result, config, customizations) {
|
|
21065
21424
|
const isFullUpdate = !options.components || options.components.length === 0;
|
|
21066
21425
|
if (isFullUpdate) {
|
|
21067
|
-
const synced = await syncRootLevelFiles(options.targetDir, options);
|
|
21426
|
+
const { synced, preserved } = await syncRootLevelFiles(options.targetDir, options, customizations);
|
|
21068
21427
|
result.syncedRootFiles = synced;
|
|
21428
|
+
result.preservedFiles.push(...preserved);
|
|
21069
21429
|
const removed = await removeDeprecatedFiles(options.targetDir, options);
|
|
21070
21430
|
result.removedDeprecatedFiles = removed;
|
|
21071
21431
|
if (!options.dryRun) {
|
|
@@ -21182,7 +21542,7 @@ async function update(options) {
|
|
|
21182
21542
|
const lockfile = await readLockfile(options.targetDir);
|
|
21183
21543
|
const components = options.components || getAllUpdateComponents();
|
|
21184
21544
|
await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
|
|
21185
|
-
await runFullUpdatePostProcessing(options, result, config);
|
|
21545
|
+
await runFullUpdatePostProcessing(options, result, config, customizations);
|
|
21186
21546
|
await regenerateLockfile(options.targetDir, result);
|
|
21187
21547
|
checkAndInstallRtkAfterUpdate();
|
|
21188
21548
|
checkAndInstallCodexAfterUpdate();
|
|
@@ -21373,19 +21733,31 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
21373
21733
|
});
|
|
21374
21734
|
return preservedFiles;
|
|
21375
21735
|
}
|
|
21376
|
-
var ROOT_LEVEL_FILES = [
|
|
21377
|
-
|
|
21736
|
+
var ROOT_LEVEL_FILES = [
|
|
21737
|
+
"statusline.sh",
|
|
21738
|
+
"install-hooks.sh",
|
|
21739
|
+
"uninstall-hooks.sh",
|
|
21740
|
+
"schemas/tool-inputs.json"
|
|
21741
|
+
];
|
|
21742
|
+
async function syncRootLevelFiles(targetDir, options, customizations) {
|
|
21378
21743
|
if (options.dryRun) {
|
|
21379
|
-
return ROOT_LEVEL_FILES;
|
|
21744
|
+
return { synced: ROOT_LEVEL_FILES, preserved: [] };
|
|
21380
21745
|
}
|
|
21381
21746
|
const fs = await import("node:fs/promises");
|
|
21382
21747
|
const layout = getProviderLayout();
|
|
21383
21748
|
const synced = [];
|
|
21749
|
+
const preserved = [];
|
|
21750
|
+
const preserveSet = new Set(customizations && !options.forceOverwriteAll ? customizations.preserveFiles : []);
|
|
21384
21751
|
for (const fileName of ROOT_LEVEL_FILES) {
|
|
21385
21752
|
const srcPath = resolveTemplatePath(join18(layout.rootDir, fileName));
|
|
21386
21753
|
if (!await fileExists(srcPath)) {
|
|
21387
21754
|
continue;
|
|
21388
21755
|
}
|
|
21756
|
+
const relPath = `${layout.rootDir}/${fileName}`;
|
|
21757
|
+
if (preserveSet.has(relPath)) {
|
|
21758
|
+
preserved.push(relPath);
|
|
21759
|
+
continue;
|
|
21760
|
+
}
|
|
21389
21761
|
const destPath = join18(targetDir, layout.rootDir, fileName);
|
|
21390
21762
|
await ensureDirectory(join18(destPath, ".."));
|
|
21391
21763
|
await fs.copyFile(srcPath, destPath);
|
|
@@ -21397,7 +21769,7 @@ async function syncRootLevelFiles(targetDir, options) {
|
|
|
21397
21769
|
if (synced.length > 0) {
|
|
21398
21770
|
debug("update.root_files_synced", { files: synced.join(", ") });
|
|
21399
21771
|
}
|
|
21400
|
-
return synced;
|
|
21772
|
+
return { synced, preserved };
|
|
21401
21773
|
}
|
|
21402
21774
|
async function removeDeprecatedFiles(targetDir, options) {
|
|
21403
21775
|
const manifestPath = resolveTemplatePath("deprecated-files.json");
|