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/index.js
CHANGED
|
@@ -151,6 +151,19 @@ async function writeJsonFile(path, data) {
|
|
|
151
151
|
const content = JSON.stringify(data, null, 2);
|
|
152
152
|
await fs.writeFile(path, content, "utf-8");
|
|
153
153
|
}
|
|
154
|
+
function parseJsonText(raw) {
|
|
155
|
+
const bom = raw.startsWith(UTF8_BOM);
|
|
156
|
+
const text = bom ? raw.slice(UTF8_BOM.length) : raw;
|
|
157
|
+
return {
|
|
158
|
+
data: JSON.parse(text),
|
|
159
|
+
format: { bom, trailingNewline: text.endsWith(`
|
|
160
|
+
`) }
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function stringifyJson(data, format) {
|
|
164
|
+
return `${format.bom ? UTF8_BOM : ""}${JSON.stringify(data, null, 2)}${format.trailingNewline ? `
|
|
165
|
+
` : ""}`;
|
|
166
|
+
}
|
|
154
167
|
async function readTextFile(path) {
|
|
155
168
|
const fs = await import("node:fs/promises");
|
|
156
169
|
return fs.readFile(path, "utf-8");
|
|
@@ -188,6 +201,7 @@ function matchesPattern(filename, pattern) {
|
|
|
188
201
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
189
202
|
return regex.test(filename);
|
|
190
203
|
}
|
|
204
|
+
var UTF8_BOM = "\uFEFF";
|
|
191
205
|
var init_fs = () => {};
|
|
192
206
|
|
|
193
207
|
// src/core/registry.ts
|
|
@@ -225,6 +239,9 @@ function registryDir() {
|
|
|
225
239
|
function registryPath() {
|
|
226
240
|
return join6(registryDir(), "projects.json");
|
|
227
241
|
}
|
|
242
|
+
function emptyRegistry() {
|
|
243
|
+
return { projects: {} };
|
|
244
|
+
}
|
|
228
245
|
async function readRegistryRaw() {
|
|
229
246
|
try {
|
|
230
247
|
const content = await readFile(registryPath(), "utf-8");
|
|
@@ -232,9 +249,9 @@ async function readRegistryRaw() {
|
|
|
232
249
|
if (typeof parsed === "object" && parsed !== null && "projects" in parsed && typeof parsed.projects === "object" && parsed.projects !== null) {
|
|
233
250
|
return parsed;
|
|
234
251
|
}
|
|
235
|
-
return
|
|
252
|
+
return emptyRegistry();
|
|
236
253
|
} catch {
|
|
237
|
-
return
|
|
254
|
+
return emptyRegistry();
|
|
238
255
|
}
|
|
239
256
|
}
|
|
240
257
|
async function writeRegistry(registry) {
|
|
@@ -258,9 +275,8 @@ async function registerProject(projectPath, version) {
|
|
|
258
275
|
};
|
|
259
276
|
await writeRegistry(registry);
|
|
260
277
|
}
|
|
261
|
-
var _registryDirOverride,
|
|
278
|
+
var _registryDirOverride, SCAN_SKIP_DIRS;
|
|
262
279
|
var init_registry = __esm(() => {
|
|
263
|
-
EMPTY_REGISTRY = { projects: {} };
|
|
264
280
|
SCAN_SKIP_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
|
|
265
281
|
});
|
|
266
282
|
|
|
@@ -330,7 +346,7 @@ var MESSAGES = {
|
|
|
330
346
|
"install.entry_md_skipped": "{{entry}} skipped ({{reason}})",
|
|
331
347
|
"install.lockfile_generated": "Lockfile generated ({{files}} files tracked)",
|
|
332
348
|
"install.lockfile_failed": "Failed to generate lockfile: {{error}}",
|
|
333
|
-
"install.hook_commands_migrated": "Migrated {{rewritten}} hook command(s) in settings.local.json",
|
|
349
|
+
"install.hook_commands_migrated": "Migrated {{rewritten}} hook/statusLine command(s) in settings.local.json",
|
|
334
350
|
"install.hook_commands_migration_failed": "Failed to migrate hook commands in settings.local.json: {{error}}",
|
|
335
351
|
"lockfile.not_found": "Lockfile not found: {{path}}",
|
|
336
352
|
"lockfile.invalid_version": "Invalid lockfile version: {{path}}",
|
|
@@ -353,15 +369,86 @@ var MESSAGES = {
|
|
|
353
369
|
"update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
|
|
354
370
|
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} updated: {{hint}}",
|
|
355
371
|
"update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
|
|
356
|
-
"update.hook_commands_migrated": "Migrated {{count}} hook command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
372
|
+
"update.hook_commands_migrated": "Migrated {{count}} hook/statusLine command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
357
373
|
"update.hook_commands_migration_failed": "Failed to migrate hook commands in {{path}}",
|
|
358
374
|
"config.load_failed": "Failed to load config: {{error}}",
|
|
359
375
|
"config.not_found": "Config not found at {{path}}, using defaults",
|
|
360
376
|
"config.saved": "Config saved to {{path}}",
|
|
361
377
|
"config.deleted": "Config deleted from {{path}}",
|
|
378
|
+
"config.invalid_preserve_path": "Ignored invalid preserve path: {{path}} ({{reason}})",
|
|
379
|
+
"codex.already_installed": "Codex CLI is already installed",
|
|
380
|
+
"codex.installing_brew": "Installing Codex CLI via Homebrew...",
|
|
381
|
+
"codex.installing_npm": "Installing Codex CLI via npm...",
|
|
382
|
+
"codex.install_failed": "Failed to install Codex CLI: {{error}}",
|
|
383
|
+
"codex.unsupported_os": "Codex CLI auto-install is not supported on {{os}}",
|
|
384
|
+
"rtk.already_installed": "RTK is already installed",
|
|
385
|
+
"rtk.installing_brew": "Installing RTK via Homebrew...",
|
|
386
|
+
"rtk.installing_curl": "Installing RTK via install script...",
|
|
387
|
+
"rtk.install_failed": "Failed to install RTK: {{error}}",
|
|
388
|
+
"rtk.unsupported_os": "RTK auto-install is not supported on {{os}}",
|
|
389
|
+
"mcp.ontology_rag_configured": "ontology-rag MCP server configured successfully",
|
|
390
|
+
"install.codex_installing": "Installing Codex CLI...",
|
|
391
|
+
"install.codex_success": "Codex CLI installed successfully",
|
|
392
|
+
"install.codex_already": "Codex CLI is already installed",
|
|
393
|
+
"install.rtk_installing": "Installing RTK...",
|
|
394
|
+
"install.rtk_success": "RTK installed successfully",
|
|
395
|
+
"install.rtk_already": "RTK is already installed",
|
|
396
|
+
"install.preserved": "Preserved {{files}} file(s) and {{dirs}} directory(ies) from the existing installation",
|
|
397
|
+
"install.restored": "Restored {{files}} file(s) and {{dirs}} directory(ies) after installation",
|
|
398
|
+
"install.backup_failed": "Failed to back up {{path}}: {{error}}",
|
|
399
|
+
"install.hooks_settings_failed": "Failed to merge hooks into settings.local.json: {{error}}",
|
|
400
|
+
"preserve.cleanup_failed": "Failed to clean up preservation directory {{dir}}: {{error}}",
|
|
401
|
+
"preserve.extract_dir_failed": "Failed to preserve directory {{dir}}: {{error}}",
|
|
402
|
+
"preserve.extract_failed": "Failed to preserve file {{file}}: {{error}}",
|
|
403
|
+
"preserve.restore_dir_failed": "Failed to restore directory {{dir}}: {{error}}",
|
|
404
|
+
"preserve.restore_failed": "Failed to restore file {{file}}: {{error}}",
|
|
405
|
+
"preserve_files.invalid_path": "Ignored invalid preserve_files entry from {{source}}: {{path}} ({{reason}})",
|
|
406
|
+
"update.codex_missing": "Codex CLI is not installed",
|
|
407
|
+
"update.rtk_missing": "RTK is not installed",
|
|
408
|
+
"update.self_update_skipped": "Skipped update: source project cannot update itself",
|
|
409
|
+
"update.deprecated_file_invalid_path": "Skipped deprecated file with invalid path: {{path}} ({{reason}})",
|
|
410
|
+
"update.deprecated_file_removed": "Removed deprecated file: {{path}} ({{reason}})",
|
|
411
|
+
"update.entry_doc_created": "Entry document created: {{path}}",
|
|
412
|
+
"update.entry_doc_force_updated": "Entry document force-updated: {{path}}",
|
|
413
|
+
"update.entry_merge_warning": "Entry document merge warning: {{warning}}",
|
|
414
|
+
"update.entry_template_not_found": "Entry document template not found: {{template}}",
|
|
415
|
+
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}} overwritten: {{hint}}",
|
|
416
|
+
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped: {{hint}}",
|
|
417
|
+
"update.settings_local_backfill_failed": "Failed to backfill statusLine refreshInterval in {{path}}",
|
|
362
418
|
"general.done": "Done!",
|
|
363
419
|
"general.failed": "Failed",
|
|
364
|
-
"general.skipped": "Skipped"
|
|
420
|
+
"general.skipped": "Skipped",
|
|
421
|
+
"install.agent_domain_excluded": "Excluded agent {{agent}} (domain: {{domain}})",
|
|
422
|
+
"install.backed_up": "Backed up {{from}} to {{to}}",
|
|
423
|
+
"install.hooks_settings_merged": "Merged hooks wiring into settings.local.json",
|
|
424
|
+
"install.hooks_settings_skipped": "Skipped hooks wiring in settings.local.json: {{reason}}",
|
|
425
|
+
"install.schemas_not_found": "schemas/tool-inputs.json template not found: {{path}}",
|
|
426
|
+
"install.schemas_skipped": "Skipped schemas/tool-inputs.json: {{reason}}",
|
|
427
|
+
"install.schemas_installed": "Installed schemas/tool-inputs.json",
|
|
428
|
+
"install.settings_local_created": "Created settings.local.json",
|
|
429
|
+
"install.settings_local_merged": "Merged statusLine into settings.local.json",
|
|
430
|
+
"install.settings_local_refreshInterval_added": "Added statusLine refreshInterval to settings.local.json",
|
|
431
|
+
"install.settings_local_skipped": "Skipped settings.local.json statusLine: {{reason}}",
|
|
432
|
+
"install.settings_local_statusline_migrated": "Re-anchored default statusLine command in settings.local.json",
|
|
433
|
+
"install.skill_scope_excluded": "Excluded skill {{skill}} (scope: {{scope}})",
|
|
434
|
+
"install.statusline_installed": "Installed statusline.sh",
|
|
435
|
+
"install.statusline_not_found": "statusline.sh template not found: {{path}}",
|
|
436
|
+
"install.statusline_skipped": "Skipped statusline.sh: {{reason}}",
|
|
437
|
+
"install.tests_config_installed": "Installed tests/tsconfig.json",
|
|
438
|
+
"install.tests_config_not_found": "tests/tsconfig.json template not found: {{path}}",
|
|
439
|
+
"install.tests_config_skipped": "Skipped tests/tsconfig.json: {{reason}}",
|
|
440
|
+
"preserve.cleanup": "Cleaned up preservation directory: {{dir}}",
|
|
441
|
+
"preserve.copied_json": "Copied JSON file: {{file}}",
|
|
442
|
+
"preserve.extracted_dir": "Preserved directory: {{dir}}",
|
|
443
|
+
"preserve.extracted_file": "Preserved file: {{file}}",
|
|
444
|
+
"preserve.merged_json": "Merged JSON file: {{file}}",
|
|
445
|
+
"preserve.restored_dir": "Restored directory: {{dir}}",
|
|
446
|
+
"preserve.restored_file": "Restored file: {{file}}",
|
|
447
|
+
"update.deprecated_files_cleaned": "Removed {{count}} deprecated file(s)",
|
|
448
|
+
"update.entry_doc_merged": "Entry document merged: {{path}} ({{managed}} managed, {{custom}} custom sections)",
|
|
449
|
+
"update.file_backed_up": "Backed up {{path}} to {{backup}}",
|
|
450
|
+
"update.root_files_synced": "Root-level files synced: {{files}}",
|
|
451
|
+
"update.settings_local_refreshInterval_backfilled": "Backfilled statusLine refreshInterval in settings.local.json"
|
|
365
452
|
},
|
|
366
453
|
ko: {
|
|
367
454
|
"install.start": "oh-my-customcode 초기화 중...",
|
|
@@ -380,7 +467,7 @@ var MESSAGES = {
|
|
|
380
467
|
"install.entry_md_skipped": "{{entry}} 건너뜀 ({{reason}})",
|
|
381
468
|
"install.lockfile_generated": "잠금 파일 생성 완료 ({{files}}개 파일 추적)",
|
|
382
469
|
"install.lockfile_failed": "잠금 파일 생성 실패: {{error}}",
|
|
383
|
-
"install.hook_commands_migrated": "settings.local.json의
|
|
470
|
+
"install.hook_commands_migrated": "settings.local.json의 훅/상태줄 명령 {{rewritten}}개를 마이그레이션했습니다",
|
|
384
471
|
"install.hook_commands_migration_failed": "settings.local.json의 훅 명령 마이그레이션에 실패했습니다: {{error}}",
|
|
385
472
|
"lockfile.not_found": "잠금 파일 없음: {{path}}",
|
|
386
473
|
"lockfile.invalid_version": "잠금 파일 버전 유효하지 않음: {{path}}",
|
|
@@ -403,15 +490,86 @@ var MESSAGES = {
|
|
|
403
490
|
"update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
|
|
404
491
|
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트됨: {{hint}}",
|
|
405
492
|
"update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
|
|
406
|
-
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로
|
|
493
|
+
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로 훅/상태줄 명령 {{count}}개를 마이그레이션했습니다",
|
|
407
494
|
"update.hook_commands_migration_failed": "{{path}}의 훅 명령 마이그레이션에 실패했습니다",
|
|
408
495
|
"config.load_failed": "설정 로드 실패: {{error}}",
|
|
409
496
|
"config.not_found": "{{path}}에 설정 없음, 기본값 사용",
|
|
410
497
|
"config.saved": "설정 저장: {{path}}",
|
|
411
498
|
"config.deleted": "설정 삭제: {{path}}",
|
|
499
|
+
"config.invalid_preserve_path": "유효하지 않은 보존 경로를 무시했습니다: {{path}} ({{reason}})",
|
|
500
|
+
"codex.already_installed": "Codex CLI가 이미 설치되어 있습니다",
|
|
501
|
+
"codex.installing_brew": "Homebrew로 Codex CLI를 설치합니다...",
|
|
502
|
+
"codex.installing_npm": "npm으로 Codex CLI를 설치합니다...",
|
|
503
|
+
"codex.install_failed": "Codex CLI 설치에 실패했습니다: {{error}}",
|
|
504
|
+
"codex.unsupported_os": "{{os}}에서는 Codex CLI 자동 설치를 지원하지 않습니다",
|
|
505
|
+
"rtk.already_installed": "RTK가 이미 설치되어 있습니다",
|
|
506
|
+
"rtk.installing_brew": "Homebrew로 RTK를 설치합니다...",
|
|
507
|
+
"rtk.installing_curl": "설치 스크립트로 RTK를 설치합니다...",
|
|
508
|
+
"rtk.install_failed": "RTK 설치에 실패했습니다: {{error}}",
|
|
509
|
+
"rtk.unsupported_os": "{{os}}에서는 RTK 자동 설치를 지원하지 않습니다",
|
|
510
|
+
"mcp.ontology_rag_configured": "ontology-rag MCP 서버 구성을 완료했습니다",
|
|
511
|
+
"install.codex_installing": "Codex CLI를 설치합니다...",
|
|
512
|
+
"install.codex_success": "Codex CLI를 설치했습니다",
|
|
513
|
+
"install.codex_already": "Codex CLI가 이미 설치되어 있습니다",
|
|
514
|
+
"install.rtk_installing": "RTK를 설치합니다...",
|
|
515
|
+
"install.rtk_success": "RTK를 설치했습니다",
|
|
516
|
+
"install.rtk_already": "RTK가 이미 설치되어 있습니다",
|
|
517
|
+
"install.preserved": "기존 설치에서 파일 {{files}}개와 디렉토리 {{dirs}}개를 보존했습니다",
|
|
518
|
+
"install.restored": "설치 후 파일 {{files}}개와 디렉토리 {{dirs}}개를 복원했습니다",
|
|
519
|
+
"install.backup_failed": "{{path}} 백업에 실패했습니다: {{error}}",
|
|
520
|
+
"install.hooks_settings_failed": "settings.local.json에 훅을 병합하지 못했습니다: {{error}}",
|
|
521
|
+
"preserve.cleanup_failed": "보존 디렉토리 {{dir}} 정리에 실패했습니다: {{error}}",
|
|
522
|
+
"preserve.extract_dir_failed": "디렉토리 {{dir}} 보존에 실패했습니다: {{error}}",
|
|
523
|
+
"preserve.extract_failed": "파일 {{file}} 보존에 실패했습니다: {{error}}",
|
|
524
|
+
"preserve.restore_dir_failed": "디렉토리 {{dir}} 복원에 실패했습니다: {{error}}",
|
|
525
|
+
"preserve.restore_failed": "파일 {{file}} 복원에 실패했습니다: {{error}}",
|
|
526
|
+
"preserve_files.invalid_path": "{{source}}의 유효하지 않은 preserve_files 항목을 무시했습니다: {{path}} ({{reason}})",
|
|
527
|
+
"update.codex_missing": "Codex CLI가 설치되어 있지 않습니다",
|
|
528
|
+
"update.rtk_missing": "RTK가 설치되어 있지 않습니다",
|
|
529
|
+
"update.self_update_skipped": "업데이트를 건너뜁니다: 소스 프로젝트는 스스로 업데이트할 수 없습니다",
|
|
530
|
+
"update.deprecated_file_invalid_path": "경로가 유효하지 않아 지원 중단 파일을 건너뜁니다: {{path}} ({{reason}})",
|
|
531
|
+
"update.deprecated_file_removed": "지원 중단 파일을 삭제했습니다: {{path}} ({{reason}})",
|
|
532
|
+
"update.entry_doc_created": "진입 문서를 생성했습니다: {{path}}",
|
|
533
|
+
"update.entry_doc_force_updated": "진입 문서를 강제로 업데이트했습니다: {{path}}",
|
|
534
|
+
"update.entry_merge_warning": "진입 문서 병합 경고: {{warning}}",
|
|
535
|
+
"update.entry_template_not_found": "진입 문서 템플릿을 찾을 수 없습니다: {{template}}",
|
|
536
|
+
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}}을(를) 덮어썼습니다: {{hint}}",
|
|
537
|
+
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다: {{hint}}",
|
|
538
|
+
"update.settings_local_backfill_failed": "{{path}}의 statusLine refreshInterval 보충에 실패했습니다",
|
|
412
539
|
"general.done": "완료!",
|
|
413
540
|
"general.failed": "실패",
|
|
414
|
-
"general.skipped": "건너뜀"
|
|
541
|
+
"general.skipped": "건너뜀",
|
|
542
|
+
"install.agent_domain_excluded": "에이전트 {{agent}}을(를) 제외했습니다 (도메인: {{domain}})",
|
|
543
|
+
"install.backed_up": "{{from}}을(를) {{to}}(으)로 백업했습니다",
|
|
544
|
+
"install.hooks_settings_merged": "settings.local.json에 훅 연결을 병합했습니다",
|
|
545
|
+
"install.hooks_settings_skipped": "settings.local.json의 훅 연결을 건너뜁니다: {{reason}}",
|
|
546
|
+
"install.schemas_not_found": "schemas/tool-inputs.json 템플릿을 찾을 수 없습니다: {{path}}",
|
|
547
|
+
"install.schemas_skipped": "schemas/tool-inputs.json 설치를 건너뜁니다: {{reason}}",
|
|
548
|
+
"install.schemas_installed": "schemas/tool-inputs.json을 설치했습니다",
|
|
549
|
+
"install.settings_local_created": "settings.local.json을 생성했습니다",
|
|
550
|
+
"install.settings_local_merged": "settings.local.json에 statusLine을 병합했습니다",
|
|
551
|
+
"install.settings_local_refreshInterval_added": "settings.local.json에 statusLine refreshInterval을 추가했습니다",
|
|
552
|
+
"install.settings_local_skipped": "settings.local.json의 statusLine을 건너뜁니다: {{reason}}",
|
|
553
|
+
"install.settings_local_statusline_migrated": "settings.local.json의 기본 statusLine 명령을 프로젝트 루트 기준으로 변경했습니다",
|
|
554
|
+
"install.skill_scope_excluded": "스킬 {{skill}}을(를) 제외했습니다 (범위: {{scope}})",
|
|
555
|
+
"install.statusline_installed": "statusline.sh를 설치했습니다",
|
|
556
|
+
"install.statusline_not_found": "statusline.sh 템플릿을 찾을 수 없습니다: {{path}}",
|
|
557
|
+
"install.statusline_skipped": "statusline.sh 설치를 건너뜁니다: {{reason}}",
|
|
558
|
+
"install.tests_config_installed": "tests/tsconfig.json을 설치했습니다",
|
|
559
|
+
"install.tests_config_not_found": "tests/tsconfig.json 템플릿을 찾을 수 없습니다: {{path}}",
|
|
560
|
+
"install.tests_config_skipped": "tests/tsconfig.json 설치를 건너뜁니다: {{reason}}",
|
|
561
|
+
"preserve.cleanup": "보존 디렉토리를 정리했습니다: {{dir}}",
|
|
562
|
+
"preserve.copied_json": "JSON 파일을 복사했습니다: {{file}}",
|
|
563
|
+
"preserve.extracted_dir": "디렉토리를 보존했습니다: {{dir}}",
|
|
564
|
+
"preserve.extracted_file": "파일을 보존했습니다: {{file}}",
|
|
565
|
+
"preserve.merged_json": "JSON 파일을 병합했습니다: {{file}}",
|
|
566
|
+
"preserve.restored_dir": "디렉토리를 복원했습니다: {{dir}}",
|
|
567
|
+
"preserve.restored_file": "파일을 복원했습니다: {{file}}",
|
|
568
|
+
"update.deprecated_files_cleaned": "지원 중단 파일 {{count}}개를 삭제했습니다",
|
|
569
|
+
"update.entry_doc_merged": "진입 문서를 병합했습니다: {{path}} (관리 섹션 {{managed}}개, 사용자 섹션 {{custom}}개)",
|
|
570
|
+
"update.file_backed_up": "{{path}}을(를) {{backup}}(으)로 백업했습니다",
|
|
571
|
+
"update.root_files_synced": "루트 수준 파일을 동기화했습니다: {{files}}",
|
|
572
|
+
"update.settings_local_refreshInterval_backfilled": "settings.local.json의 statusLine refreshInterval을 보충했습니다"
|
|
415
573
|
}
|
|
416
574
|
};
|
|
417
575
|
function createLogger(options = {}) {
|
|
@@ -893,7 +1051,7 @@ import {
|
|
|
893
1051
|
rename,
|
|
894
1052
|
stat as stat2
|
|
895
1053
|
} from "node:fs/promises";
|
|
896
|
-
import { basename as basename2, join as join5 } from "node:path";
|
|
1054
|
+
import { basename as basename2, dirname as dirname3, join as join5 } from "node:path";
|
|
897
1055
|
|
|
898
1056
|
// src/core/codex-installer.ts
|
|
899
1057
|
import { execSync } from "node:child_process";
|
|
@@ -957,6 +1115,117 @@ function installCodex(deps = defaultDeps) {
|
|
|
957
1115
|
// src/core/file-preservation.ts
|
|
958
1116
|
init_fs();
|
|
959
1117
|
import { basename, join as join3 } from "node:path";
|
|
1118
|
+
|
|
1119
|
+
// src/core/hook-group-merge.ts
|
|
1120
|
+
var LEGACY_OMCUSTOM_DESCRIPTIONS = [
|
|
1121
|
+
"Record agent outcomes + auto-continue workflow + R007/R008 drift advisory on subagent completion (autonomous-loop re-entry, #1545)",
|
|
1122
|
+
'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)'
|
|
1123
|
+
];
|
|
1124
|
+
var STANDALONE_SCRIPT_COMMAND = /^(?:bash )?(?:"(?:\$\{CLAUDE_PROJECT_DIR:-\.\}\/|\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)"|(?:\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh))$/;
|
|
1125
|
+
var SCRIPT_REFERENCE = /\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)/g;
|
|
1126
|
+
function matcherKey(group) {
|
|
1127
|
+
return typeof group.matcher === "string" ? group.matcher : "";
|
|
1128
|
+
}
|
|
1129
|
+
function isRecord(value) {
|
|
1130
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1131
|
+
}
|
|
1132
|
+
function addHookOwnership(hook, owned) {
|
|
1133
|
+
if (!isRecord(hook) || typeof hook.command !== "string") {
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
owned.commands.add(hook.command);
|
|
1137
|
+
for (const match of hook.command.matchAll(SCRIPT_REFERENCE)) {
|
|
1138
|
+
owned.scripts.add(match[1]);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function addGroupOwnership(group, owned, descriptions) {
|
|
1142
|
+
if (!isRecord(group)) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
const key = matcherKey(group);
|
|
1146
|
+
let forMatcher = owned.get(key);
|
|
1147
|
+
if (forMatcher === undefined) {
|
|
1148
|
+
forMatcher = { commands: new Set, scripts: new Set };
|
|
1149
|
+
owned.set(key, forMatcher);
|
|
1150
|
+
}
|
|
1151
|
+
if (typeof group.description === "string") {
|
|
1152
|
+
descriptions.add(group.description);
|
|
1153
|
+
}
|
|
1154
|
+
if (Array.isArray(group.hooks)) {
|
|
1155
|
+
for (const hook of group.hooks) {
|
|
1156
|
+
addHookOwnership(hook, forMatcher);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function collectOwnership(generated) {
|
|
1161
|
+
const descriptions = new Set(LEGACY_OMCUSTOM_DESCRIPTIONS);
|
|
1162
|
+
const events = new Map;
|
|
1163
|
+
for (const [event, groups] of Object.entries(generated)) {
|
|
1164
|
+
const owned = new Map;
|
|
1165
|
+
events.set(event, owned);
|
|
1166
|
+
for (const group of groups) {
|
|
1167
|
+
addGroupOwnership(group, owned, descriptions);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return { descriptions, events };
|
|
1171
|
+
}
|
|
1172
|
+
function isOwnedCommand(hook, owned) {
|
|
1173
|
+
if (owned === undefined || !isRecord(hook) || hook.type !== "command") {
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
const command = hook.command;
|
|
1177
|
+
if (typeof command !== "string") {
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
if (owned.commands.has(command)) {
|
|
1181
|
+
return true;
|
|
1182
|
+
}
|
|
1183
|
+
const match = STANDALONE_SCRIPT_COMMAND.exec(command);
|
|
1184
|
+
const script = match?.[1] ?? match?.[2];
|
|
1185
|
+
return script !== undefined && owned.scripts.has(script);
|
|
1186
|
+
}
|
|
1187
|
+
function userResidual(group, owned, ownership) {
|
|
1188
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
1189
|
+
return [group];
|
|
1190
|
+
}
|
|
1191
|
+
if (typeof group.description === "string" && ownership.descriptions.has(group.description)) {
|
|
1192
|
+
return [];
|
|
1193
|
+
}
|
|
1194
|
+
const ownedForMatcher = owned?.get(matcherKey(group));
|
|
1195
|
+
const remaining = group.hooks.filter((hook) => !isOwnedCommand(hook, ownedForMatcher));
|
|
1196
|
+
if (remaining.length === group.hooks.length) {
|
|
1197
|
+
return [group];
|
|
1198
|
+
}
|
|
1199
|
+
return remaining.length === 0 ? [] : [{ ...group, hooks: remaining }];
|
|
1200
|
+
}
|
|
1201
|
+
function mergeHookBlocks(existing, generated) {
|
|
1202
|
+
const result = {};
|
|
1203
|
+
for (const [event, groups] of Object.entries(generated)) {
|
|
1204
|
+
result[event] = [...groups];
|
|
1205
|
+
}
|
|
1206
|
+
if (!isRecord(existing)) {
|
|
1207
|
+
return result;
|
|
1208
|
+
}
|
|
1209
|
+
const ownership = collectOwnership(generated);
|
|
1210
|
+
for (const [event, value] of Object.entries(existing)) {
|
|
1211
|
+
if (!Array.isArray(value)) {
|
|
1212
|
+
if (!ownership.events.has(event)) {
|
|
1213
|
+
result[event] = value;
|
|
1214
|
+
}
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
const owned = ownership.events.get(event);
|
|
1218
|
+
const residual = value.flatMap((group) => userResidual(group, owned, ownership));
|
|
1219
|
+
if (residual.length === 0) {
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
const current = result[event];
|
|
1223
|
+
result[event] = Array.isArray(current) ? [...current, ...residual] : residual;
|
|
1224
|
+
}
|
|
1225
|
+
return result;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// src/core/file-preservation.ts
|
|
960
1229
|
var DEFAULT_CRITICAL_FILES = ["settings.json", "settings.local.json"];
|
|
961
1230
|
var DEFAULT_CRITICAL_DIRECTORIES = ["agent-memory", "agent-memory-local"];
|
|
962
1231
|
var PROTECTED_FRAMEWORK_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
@@ -1066,12 +1335,19 @@ async function restoreCriticalFiles(rootDir, preservation) {
|
|
|
1066
1335
|
}
|
|
1067
1336
|
return result;
|
|
1068
1337
|
}
|
|
1338
|
+
var HOOKS_MERGE_FILES = new Set(["settings.json", "settings.local.json"]);
|
|
1339
|
+
function isGeneratedHookBlock(value) {
|
|
1340
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((groups) => Array.isArray(groups));
|
|
1341
|
+
}
|
|
1069
1342
|
async function mergeJsonFile(preservedPath, targetPath) {
|
|
1070
|
-
const preservedData = await
|
|
1343
|
+
const { data: preservedData, format } = parseJsonText(await readTextFile(preservedPath));
|
|
1071
1344
|
if (await fileExists(targetPath)) {
|
|
1072
|
-
const targetData = await
|
|
1345
|
+
const { data: targetData } = parseJsonText(await readTextFile(targetPath));
|
|
1073
1346
|
const merged = deepMerge(targetData, preservedData);
|
|
1074
|
-
|
|
1347
|
+
if (HOOKS_MERGE_FILES.has(basename(targetPath)) && isGeneratedHookBlock(targetData.hooks)) {
|
|
1348
|
+
merged.hooks = mergeHookBlocks(preservedData.hooks, targetData.hooks);
|
|
1349
|
+
}
|
|
1350
|
+
await writeTextFile(targetPath, stringifyJson(merged, format));
|
|
1075
1351
|
debug("preserve.merged_json", { file: basename(targetPath) });
|
|
1076
1352
|
} else {
|
|
1077
1353
|
await copyFile(preservedPath, targetPath);
|
|
@@ -1112,14 +1388,14 @@ function rewriteRelativeHookCommand(command) {
|
|
|
1112
1388
|
const interpreter = match[1] ?? "";
|
|
1113
1389
|
return `${interpreter}"\${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/${match[2]}"`;
|
|
1114
1390
|
}
|
|
1115
|
-
function
|
|
1391
|
+
function isRecord2(value) {
|
|
1116
1392
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1117
1393
|
}
|
|
1118
1394
|
function rewriteNode(node, counter) {
|
|
1119
1395
|
if (Array.isArray(node)) {
|
|
1120
1396
|
return node.map((item) => rewriteNode(item, counter));
|
|
1121
1397
|
}
|
|
1122
|
-
if (!
|
|
1398
|
+
if (!isRecord2(node)) {
|
|
1123
1399
|
return node;
|
|
1124
1400
|
}
|
|
1125
1401
|
const out = {};
|
|
@@ -1136,16 +1412,32 @@ function rewriteNode(node, counter) {
|
|
|
1136
1412
|
}
|
|
1137
1413
|
return out;
|
|
1138
1414
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1415
|
+
var OLD_STATUSLINE_COMMAND = ".claude/statusline.sh";
|
|
1416
|
+
var NEW_STATUSLINE_COMMAND = `bash "\${CLAUDE_PROJECT_DIR:-.}/.claude/statusline.sh"`;
|
|
1417
|
+
function migrateStatusLine(statusLine) {
|
|
1418
|
+
if (!isRecord2(statusLine) || statusLine.command !== OLD_STATUSLINE_COMMAND) {
|
|
1419
|
+
return null;
|
|
1142
1420
|
}
|
|
1421
|
+
return { ...statusLine, command: NEW_STATUSLINE_COMMAND };
|
|
1422
|
+
}
|
|
1423
|
+
function migrateHookCommands(settings) {
|
|
1143
1424
|
const counter = { count: 0 };
|
|
1144
|
-
const
|
|
1425
|
+
const updates = {};
|
|
1426
|
+
if (isRecord2(settings.hooks)) {
|
|
1427
|
+
const hooks = rewriteNode(settings.hooks, counter);
|
|
1428
|
+
if (counter.count > 0) {
|
|
1429
|
+
updates.hooks = hooks;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
const statusLine = migrateStatusLine(settings.statusLine);
|
|
1433
|
+
if (statusLine !== null) {
|
|
1434
|
+
counter.count++;
|
|
1435
|
+
updates.statusLine = statusLine;
|
|
1436
|
+
}
|
|
1145
1437
|
if (counter.count === 0) {
|
|
1146
1438
|
return { settings, rewritten: 0 };
|
|
1147
1439
|
}
|
|
1148
|
-
return { settings: { ...settings,
|
|
1440
|
+
return { settings: { ...settings, ...updates }, rewritten: counter.count };
|
|
1149
1441
|
}
|
|
1150
1442
|
|
|
1151
1443
|
// src/core/hooks-settings.ts
|
|
@@ -1494,7 +1786,7 @@ var CLASSIFICATION_TABLE = [
|
|
|
1494
1786
|
event: "PreToolUse",
|
|
1495
1787
|
description: "Block rule file deletion — requires individual user confirmation per rule (R001 safety)",
|
|
1496
1788
|
decision: "drop-condition",
|
|
1497
|
-
reason: 'rule-deletion-guard.sh explicitly re-checks `tool != "Bash"` (passthrough) and
|
|
1789
|
+
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'
|
|
1498
1790
|
},
|
|
1499
1791
|
{
|
|
1500
1792
|
event: "PostToolUse",
|
|
@@ -1618,20 +1910,44 @@ function convertHooksJson(hooksJson) {
|
|
|
1618
1910
|
}
|
|
1619
1911
|
return { hooks: result, warnings };
|
|
1620
1912
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1913
|
+
var UTF8_BOM2 = "\uFEFF";
|
|
1914
|
+
function isPlainObject(value) {
|
|
1915
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1916
|
+
}
|
|
1917
|
+
async function readExistingSettings(settingsPath) {
|
|
1918
|
+
let raw;
|
|
1625
1919
|
try {
|
|
1626
|
-
|
|
1627
|
-
settings = JSON.parse(existingRaw);
|
|
1920
|
+
raw = await readTextFile(settingsPath);
|
|
1628
1921
|
} catch {
|
|
1629
|
-
settings
|
|
1922
|
+
return { ok: true, settings: {}, bom: false };
|
|
1923
|
+
}
|
|
1924
|
+
const bom = raw.startsWith(UTF8_BOM2);
|
|
1925
|
+
if ((bom ? raw.slice(UTF8_BOM2.length) : raw).trim() === "") {
|
|
1926
|
+
return { ok: true, settings: {}, bom };
|
|
1630
1927
|
}
|
|
1631
|
-
|
|
1928
|
+
try {
|
|
1929
|
+
const { data } = parseJsonText(raw);
|
|
1930
|
+
if (isPlainObject(data)) {
|
|
1931
|
+
return { ok: true, settings: data, bom };
|
|
1932
|
+
}
|
|
1933
|
+
return { ok: false, reason: "the top-level JSON value is not a JSON object", bom };
|
|
1934
|
+
} catch (err) {
|
|
1935
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err), bom };
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
async function mergeHooksIntoSettings(settingsPath, hooksJsonPath, options = {}) {
|
|
1939
|
+
const hooksJson = await readJsonFile(hooksJsonPath);
|
|
1940
|
+
const { hooks, warnings } = convertHooksJson(hooksJson);
|
|
1941
|
+
const preserve = options.preserveUserHooks === true;
|
|
1942
|
+
const existing = await readExistingSettings(settingsPath);
|
|
1943
|
+
if (!existing.ok && preserve) {
|
|
1944
|
+
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\``);
|
|
1945
|
+
return { warnings };
|
|
1946
|
+
}
|
|
1947
|
+
const settings = existing.ok ? existing.settings : {};
|
|
1948
|
+
settings.hooks = preserve ? mergeHookBlocks(settings.hooks, hooks) : hooks;
|
|
1632
1949
|
await ensureDirectory(dirname2(settingsPath));
|
|
1633
|
-
await writeTextFile(settingsPath,
|
|
1634
|
-
`);
|
|
1950
|
+
await writeTextFile(settingsPath, stringifyJson(settings, { bom: existing.bom, trailingNewline: true }));
|
|
1635
1951
|
return { warnings };
|
|
1636
1952
|
}
|
|
1637
1953
|
|
|
@@ -2008,6 +2324,25 @@ async function installStatusline(targetDir, options, _result) {
|
|
|
2008
2324
|
await fs.chmod(destPath, 493);
|
|
2009
2325
|
debug("install.statusline_installed", {});
|
|
2010
2326
|
}
|
|
2327
|
+
async function installSchemas(targetDir, options, _result) {
|
|
2328
|
+
const layout = getProviderLayout();
|
|
2329
|
+
const relPath = join5(layout.rootDir, "schemas", "tool-inputs.json");
|
|
2330
|
+
const srcPath = resolveTemplatePath(relPath);
|
|
2331
|
+
const destPath = join5(targetDir, relPath);
|
|
2332
|
+
if (!await fileExists(srcPath)) {
|
|
2333
|
+
debug("install.schemas_not_found", { path: srcPath });
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
if (await fileExists(destPath)) {
|
|
2337
|
+
if (!options.force && !options.backup) {
|
|
2338
|
+
debug("install.schemas_skipped", { reason: "exists" });
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
await ensureDirectory(dirname3(destPath));
|
|
2343
|
+
await copyFile(srcPath, destPath);
|
|
2344
|
+
debug("install.schemas_installed", {});
|
|
2345
|
+
}
|
|
2011
2346
|
async function installTestsConfig(targetDir, options, _result) {
|
|
2012
2347
|
const srcPath = resolveTemplatePath(join5("tests", "tsconfig.json"));
|
|
2013
2348
|
const destPath = join5(targetDir, "tests", "tsconfig.json");
|
|
@@ -2024,34 +2359,55 @@ async function installTestsConfig(targetDir, options, _result) {
|
|
|
2024
2359
|
await copyFile(srcPath, destPath);
|
|
2025
2360
|
debug("install.tests_config_installed", {});
|
|
2026
2361
|
}
|
|
2362
|
+
var STATUSLINE_REFRESH_INTERVAL = 10;
|
|
2363
|
+
function updateExistingStatusLine(existing) {
|
|
2364
|
+
const migrated = migrateHookCommands({ statusLine: existing.statusLine });
|
|
2365
|
+
const commandMigrated = migrated.rewritten > 0;
|
|
2366
|
+
if (commandMigrated) {
|
|
2367
|
+
existing.statusLine = migrated.settings.statusLine;
|
|
2368
|
+
}
|
|
2369
|
+
const sl = existing.statusLine;
|
|
2370
|
+
const refreshIntervalAdded = sl.refreshInterval === undefined;
|
|
2371
|
+
if (refreshIntervalAdded) {
|
|
2372
|
+
sl.refreshInterval = STATUSLINE_REFRESH_INTERVAL;
|
|
2373
|
+
}
|
|
2374
|
+
return { commandMigrated, refreshIntervalAdded };
|
|
2375
|
+
}
|
|
2376
|
+
async function mergeStatusLineIntoExisting(settingsPath, existing, format, defaults) {
|
|
2377
|
+
if (!existing.statusLine) {
|
|
2378
|
+
existing.statusLine = defaults.statusLine;
|
|
2379
|
+
await writeTextFile(settingsPath, stringifyJson(existing, format));
|
|
2380
|
+
debug("install.settings_local_merged", {});
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
const { commandMigrated, refreshIntervalAdded } = updateExistingStatusLine(existing);
|
|
2384
|
+
if (!commandMigrated && !refreshIntervalAdded) {
|
|
2385
|
+
debug("install.settings_local_skipped", { reason: "statusLine exists" });
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
await writeTextFile(settingsPath, stringifyJson(existing, format));
|
|
2389
|
+
if (commandMigrated) {
|
|
2390
|
+
debug("install.settings_local_statusline_migrated", {});
|
|
2391
|
+
}
|
|
2392
|
+
if (refreshIntervalAdded) {
|
|
2393
|
+
debug("install.settings_local_refreshInterval_added", {});
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2027
2396
|
async function installSettingsLocal(targetDir, result) {
|
|
2028
2397
|
const layout = getProviderLayout();
|
|
2029
2398
|
const settingsPath = join5(targetDir, layout.rootDir, "settings.local.json");
|
|
2030
2399
|
const statusLineConfig = {
|
|
2031
2400
|
statusLine: {
|
|
2032
2401
|
type: "command",
|
|
2033
|
-
command:
|
|
2402
|
+
command: NEW_STATUSLINE_COMMAND,
|
|
2034
2403
|
padding: 0,
|
|
2035
|
-
refreshInterval:
|
|
2404
|
+
refreshInterval: STATUSLINE_REFRESH_INTERVAL
|
|
2036
2405
|
}
|
|
2037
2406
|
};
|
|
2038
2407
|
if (await fileExists(settingsPath)) {
|
|
2039
2408
|
try {
|
|
2040
|
-
const existing = await
|
|
2041
|
-
|
|
2042
|
-
existing.statusLine = statusLineConfig.statusLine;
|
|
2043
|
-
await writeJsonFile(settingsPath, existing);
|
|
2044
|
-
debug("install.settings_local_merged", {});
|
|
2045
|
-
} else {
|
|
2046
|
-
const sl = existing.statusLine;
|
|
2047
|
-
if (sl.refreshInterval === undefined) {
|
|
2048
|
-
sl.refreshInterval = statusLineConfig.statusLine.refreshInterval;
|
|
2049
|
-
await writeJsonFile(settingsPath, existing);
|
|
2050
|
-
debug("install.settings_local_refreshInterval_added", {});
|
|
2051
|
-
} else {
|
|
2052
|
-
debug("install.settings_local_skipped", { reason: "statusLine exists" });
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2409
|
+
const { data: existing, format } = parseJsonText(await readTextFile(settingsPath));
|
|
2410
|
+
await mergeStatusLineIntoExisting(settingsPath, existing, format, statusLineConfig);
|
|
2055
2411
|
} catch {
|
|
2056
2412
|
result.warnings.push("Failed to parse existing settings.local.json, skipping statusLine config");
|
|
2057
2413
|
}
|
|
@@ -2069,7 +2425,9 @@ async function installHooksSettings(targetDir, _options, result) {
|
|
|
2069
2425
|
return;
|
|
2070
2426
|
}
|
|
2071
2427
|
try {
|
|
2072
|
-
const mergeResult = await mergeHooksIntoSettings(settingsPath, hooksJsonPath
|
|
2428
|
+
const mergeResult = await mergeHooksIntoSettings(settingsPath, hooksJsonPath, {
|
|
2429
|
+
preserveUserHooks: true
|
|
2430
|
+
});
|
|
2073
2431
|
if (mergeResult && Array.isArray(mergeResult.warnings) && mergeResult.warnings.length > 0) {
|
|
2074
2432
|
result.warnings.push(...mergeResult.warnings);
|
|
2075
2433
|
}
|
|
@@ -2162,6 +2520,7 @@ async function install(options) {
|
|
|
2162
2520
|
await verifyTemplateDirectory();
|
|
2163
2521
|
await installAllComponents(options.targetDir, options, result);
|
|
2164
2522
|
await installStatusline(options.targetDir, options, result);
|
|
2523
|
+
await installSchemas(options.targetDir, options, result);
|
|
2165
2524
|
await installTestsConfig(options.targetDir, options, result);
|
|
2166
2525
|
await installSettingsLocal(options.targetDir, result);
|
|
2167
2526
|
await installHooksSettings(options.targetDir, options, result);
|
|
@@ -2407,7 +2766,7 @@ var package_default = {
|
|
|
2407
2766
|
workspaces: [
|
|
2408
2767
|
"packages/*"
|
|
2409
2768
|
],
|
|
2410
|
-
version: "1.1.
|
|
2769
|
+
version: "1.1.95",
|
|
2411
2770
|
description: "Batteries-included agent harness for Claude Code",
|
|
2412
2771
|
type: "module",
|
|
2413
2772
|
bin: {
|
|
@@ -5270,11 +5629,12 @@ async function migrateHookCommandsInSettingsLocal(targetDir, options) {
|
|
|
5270
5629
|
});
|
|
5271
5630
|
}
|
|
5272
5631
|
}
|
|
5273
|
-
async function runFullUpdatePostProcessing(options, result, config) {
|
|
5632
|
+
async function runFullUpdatePostProcessing(options, result, config, customizations) {
|
|
5274
5633
|
const isFullUpdate = !options.components || options.components.length === 0;
|
|
5275
5634
|
if (isFullUpdate) {
|
|
5276
|
-
const synced = await syncRootLevelFiles(options.targetDir, options);
|
|
5635
|
+
const { synced, preserved } = await syncRootLevelFiles(options.targetDir, options, customizations);
|
|
5277
5636
|
result.syncedRootFiles = synced;
|
|
5637
|
+
result.preservedFiles.push(...preserved);
|
|
5278
5638
|
const removed = await removeDeprecatedFiles(options.targetDir, options);
|
|
5279
5639
|
result.removedDeprecatedFiles = removed;
|
|
5280
5640
|
if (!options.dryRun) {
|
|
@@ -5391,7 +5751,7 @@ async function update(options) {
|
|
|
5391
5751
|
const lockfile = await readLockfile(options.targetDir);
|
|
5392
5752
|
const components = options.components || getAllUpdateComponents();
|
|
5393
5753
|
await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
|
|
5394
|
-
await runFullUpdatePostProcessing(options, result, config);
|
|
5754
|
+
await runFullUpdatePostProcessing(options, result, config, customizations);
|
|
5395
5755
|
await regenerateLockfile(options.targetDir, result);
|
|
5396
5756
|
checkAndInstallRtkAfterUpdate();
|
|
5397
5757
|
checkAndInstallCodexAfterUpdate();
|
|
@@ -5603,19 +5963,31 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
5603
5963
|
});
|
|
5604
5964
|
return preservedFiles;
|
|
5605
5965
|
}
|
|
5606
|
-
var ROOT_LEVEL_FILES = [
|
|
5607
|
-
|
|
5966
|
+
var ROOT_LEVEL_FILES = [
|
|
5967
|
+
"statusline.sh",
|
|
5968
|
+
"install-hooks.sh",
|
|
5969
|
+
"uninstall-hooks.sh",
|
|
5970
|
+
"schemas/tool-inputs.json"
|
|
5971
|
+
];
|
|
5972
|
+
async function syncRootLevelFiles(targetDir, options, customizations) {
|
|
5608
5973
|
if (options.dryRun) {
|
|
5609
|
-
return ROOT_LEVEL_FILES;
|
|
5974
|
+
return { synced: ROOT_LEVEL_FILES, preserved: [] };
|
|
5610
5975
|
}
|
|
5611
5976
|
const fs = await import("node:fs/promises");
|
|
5612
5977
|
const layout = getProviderLayout();
|
|
5613
5978
|
const synced = [];
|
|
5979
|
+
const preserved = [];
|
|
5980
|
+
const preserveSet = new Set(customizations && !options.forceOverwriteAll ? customizations.preserveFiles : []);
|
|
5614
5981
|
for (const fileName of ROOT_LEVEL_FILES) {
|
|
5615
5982
|
const srcPath = resolveTemplatePath(join7(layout.rootDir, fileName));
|
|
5616
5983
|
if (!await fileExists(srcPath)) {
|
|
5617
5984
|
continue;
|
|
5618
5985
|
}
|
|
5986
|
+
const relPath = `${layout.rootDir}/${fileName}`;
|
|
5987
|
+
if (preserveSet.has(relPath)) {
|
|
5988
|
+
preserved.push(relPath);
|
|
5989
|
+
continue;
|
|
5990
|
+
}
|
|
5619
5991
|
const destPath = join7(targetDir, layout.rootDir, fileName);
|
|
5620
5992
|
await ensureDirectory(join7(destPath, ".."));
|
|
5621
5993
|
await fs.copyFile(srcPath, destPath);
|
|
@@ -5627,7 +5999,7 @@ async function syncRootLevelFiles(targetDir, options) {
|
|
|
5627
5999
|
if (synced.length > 0) {
|
|
5628
6000
|
debug("update.root_files_synced", { files: synced.join(", ") });
|
|
5629
6001
|
}
|
|
5630
|
-
return synced;
|
|
6002
|
+
return { synced, preserved };
|
|
5631
6003
|
}
|
|
5632
6004
|
async function removeDeprecatedFiles(targetDir, options) {
|
|
5633
6005
|
const manifestPath = resolveTemplatePath("deprecated-files.json");
|