oh-my-customcode 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -12097,6 +12097,10 @@ var en_default = {
12097
12097
  doctor: {
12098
12098
  description: "Check and fix configuration issues",
12099
12099
  fixOption: "Automatically fix issues that can be fixed",
12100
+ updatesOption: "Check for oh-my-customcode updates",
12101
+ updateAvailable: "Update available: v{{current}} → v{{latest}}. Run 'omcustom update' to apply.",
12102
+ updateUpToDate: "oh-my-customcode is up to date (v{{version}})",
12103
+ updateCheckFailed: "Update check failed ({{reason}})",
12100
12104
  checking: "Running diagnostic checks...",
12101
12105
  applyingFixes: "Applying fixes...",
12102
12106
  fixing: "Fixing: {{name}}...",
@@ -12413,6 +12417,10 @@ var ko_default = {
12413
12417
  doctor: {
12414
12418
  description: "설정 문제 확인 및 수정",
12415
12419
  fixOption: "자동으로 수정 가능한 문제 수정",
12420
+ updatesOption: "oh-my-customcode 업데이트 확인",
12421
+ updateAvailable: "업데이트 가능: v{{current}} → v{{latest}}. 'omcustom update'를 실행하여 적용하세요.",
12422
+ updateUpToDate: "oh-my-customcode가 최신 상태입니다 (v{{version}})",
12423
+ updateCheckFailed: "업데이트 확인 실패 ({{reason}})",
12416
12424
  checking: "진단 검사 실행 중...",
12417
12425
  applyingFixes: "수정 사항 적용 중...",
12418
12426
  fixing: "수정 중: {{name}}...",
@@ -12865,8 +12873,9 @@ async function maybeHandleSelfUpdateForInit(options) {
12865
12873
  }
12866
12874
 
12867
12875
  // src/cli/doctor.ts
12868
- import { constants, promises as fs } from "node:fs";
12876
+ import { constants, promises as fs, readFileSync as readFileSync2 } from "node:fs";
12869
12877
  import path from "node:path";
12878
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
12870
12879
 
12871
12880
  // node_modules/yaml/dist/index.js
12872
12881
  var composer = require_composer();
@@ -12978,6 +12987,16 @@ var MESSAGES = {
12978
12987
  "install.entry_md_installed": "{{entry}} installed ({{language}})",
12979
12988
  "install.entry_md_not_found": "{{entry}} template not found for {{language}}",
12980
12989
  "install.entry_md_skipped": "{{entry}} skipped ({{reason}})",
12990
+ "install.lockfile_generated": "Lockfile generated ({{files}} files tracked)",
12991
+ "install.lockfile_failed": "Failed to generate lockfile: {{error}}",
12992
+ "lockfile.not_found": "Lockfile not found: {{path}}",
12993
+ "lockfile.invalid_version": "Invalid lockfile version: {{path}}",
12994
+ "lockfile.invalid_structure": "Invalid lockfile structure: {{path}}",
12995
+ "lockfile.read_failed": "Failed to read lockfile: {{path}} — {{error}}",
12996
+ "lockfile.written": "Lockfile written: {{path}}",
12997
+ "lockfile.component_dir_missing": "Component directory missing: {{path}}",
12998
+ "lockfile.hash_failed": "Failed to hash file: {{path}} — {{error}}",
12999
+ "lockfile.entry_added": "Lockfile entry added: {{path}} ({{component}})",
12981
13000
  "update.start": "Checking for updates...",
12982
13001
  "update.success": "Updated from {{from}} to {{to}}",
12983
13002
  "update.components_synced": "Components synced (version {{version}}): {{components}}",
@@ -12987,6 +13006,8 @@ var MESSAGES = {
12987
13006
  "update.dry_run": "Would update {{component}}",
12988
13007
  "update.component_updated": "Updated {{component}}",
12989
13008
  "update.file_applied": "Applied update to {{path}}",
13009
+ "update.lockfile_regenerated": "Lockfile regenerated ({{files}} files tracked)",
13010
+ "update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
12990
13011
  "config.load_failed": "Failed to load config: {{error}}",
12991
13012
  "config.not_found": "Config not found at {{path}}, using defaults",
12992
13013
  "config.saved": "Config saved to {{path}}",
@@ -13010,6 +13031,16 @@ var MESSAGES = {
13010
13031
  "install.entry_md_installed": "{{entry}} 설치 완료 ({{language}})",
13011
13032
  "install.entry_md_not_found": "{{language}}용 {{entry}} 템플릿 없음",
13012
13033
  "install.entry_md_skipped": "{{entry}} 건너뜀 ({{reason}})",
13034
+ "install.lockfile_generated": "잠금 파일 생성 완료 ({{files}}개 파일 추적)",
13035
+ "install.lockfile_failed": "잠금 파일 생성 실패: {{error}}",
13036
+ "lockfile.not_found": "잠금 파일 없음: {{path}}",
13037
+ "lockfile.invalid_version": "잠금 파일 버전 유효하지 않음: {{path}}",
13038
+ "lockfile.invalid_structure": "잠금 파일 구조 유효하지 않음: {{path}}",
13039
+ "lockfile.read_failed": "잠금 파일 읽기 실패: {{path}} — {{error}}",
13040
+ "lockfile.written": "잠금 파일 기록됨: {{path}}",
13041
+ "lockfile.component_dir_missing": "컴포넌트 디렉토리 없음: {{path}}",
13042
+ "lockfile.hash_failed": "파일 해시 실패: {{path}} — {{error}}",
13043
+ "lockfile.entry_added": "잠금 파일 항목 추가: {{path}} ({{component}})",
13013
13044
  "update.start": "업데이트 확인 중...",
13014
13045
  "update.success": "{{from}}에서 {{to}}로 업데이트 완료",
13015
13046
  "update.components_synced": "컴포넌트 동기화 완료 (버전 {{version}}): {{components}}",
@@ -13019,6 +13050,8 @@ var MESSAGES = {
13019
13050
  "update.dry_run": "{{component}} 업데이트 예정",
13020
13051
  "update.component_updated": "{{component}} 업데이트 완료",
13021
13052
  "update.file_applied": "{{path}} 업데이트 적용",
13053
+ "update.lockfile_regenerated": "잠금 파일 재생성 완료 ({{files}}개 파일 추적)",
13054
+ "update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
13022
13055
  "config.load_failed": "설정 로드 실패: {{error}}",
13023
13056
  "config.not_found": "{{path}}에 설정 없음, 기본값 사용",
13024
13057
  "config.saved": "설정 저장: {{path}}",
@@ -13713,12 +13746,51 @@ function printCheck(check) {
13713
13746
  }
13714
13747
  }
13715
13748
  }
13749
+ function readCurrentVersion() {
13750
+ try {
13751
+ const __filename2 = fileURLToPath2(import.meta.url);
13752
+ const packageJsonPath = path.resolve(path.dirname(__filename2), "../../package.json");
13753
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
13754
+ return packageJson.version;
13755
+ } catch {
13756
+ return "0.0.0";
13757
+ }
13758
+ }
13759
+ function checkUpdateAvailable(currentVersion) {
13760
+ const result = checkSelfUpdate({ currentVersion });
13761
+ if (!result.checked) {
13762
+ return {
13763
+ name: "Update",
13764
+ status: "warn",
13765
+ message: i18n.t("cli.doctor.updateCheckFailed", { reason: result.reason ?? "unknown" }),
13766
+ fixable: false
13767
+ };
13768
+ }
13769
+ if (result.updateAvailable && result.latestVersion !== null) {
13770
+ return {
13771
+ name: "Update",
13772
+ status: "warn",
13773
+ message: i18n.t("cli.doctor.updateAvailable", {
13774
+ current: currentVersion,
13775
+ latest: result.latestVersion
13776
+ }),
13777
+ fixable: false,
13778
+ details: result.usedCache ? ["(checked from cache)"] : ["(checked from npm registry)"]
13779
+ };
13780
+ }
13781
+ return {
13782
+ name: "Update",
13783
+ status: "pass",
13784
+ message: i18n.t("cli.doctor.updateUpToDate", { version: currentVersion }),
13785
+ fixable: false
13786
+ };
13787
+ }
13716
13788
  async function doctorCommand(options = {}) {
13717
13789
  const targetDir = process.cwd();
13718
13790
  console.log(i18n.t("cli.doctor.checking"));
13719
13791
  console.log("");
13720
13792
  const layout = getProviderLayout();
13721
- let checks = await Promise.all([
13793
+ const baseChecks = await Promise.all([
13722
13794
  checkEntryDoc(targetDir, layout.entryFile),
13723
13795
  checkRules(targetDir, layout.rootDir),
13724
13796
  checkAgents(targetDir, layout.rootDir),
@@ -13730,12 +13802,14 @@ async function doctorCommand(options = {}) {
13730
13802
  checkContexts(targetDir, layout.rootDir),
13731
13803
  checkCustomComponents(targetDir, layout.rootDir)
13732
13804
  ]);
13805
+ const checksWithUpdate = options.updates ? [...baseChecks, checkUpdateAvailable(readCurrentVersion())] : baseChecks;
13806
+ let checks = checksWithUpdate;
13733
13807
  if (options.fix) {
13734
- const hasFixableIssues = checks.some((c) => c.status === "fail" && c.fixable);
13808
+ const hasFixableIssues = checksWithUpdate.some((c) => c.status === "fail" && c.fixable);
13735
13809
  if (hasFixableIssues) {
13736
13810
  console.log(i18n.t("cli.doctor.applyingFixes"));
13737
13811
  console.log("");
13738
- checks = await fixIssues(checks, targetDir, layout.rootDir);
13812
+ checks = await fixIssues(checksWithUpdate, targetDir, layout.rootDir);
13739
13813
  console.log("");
13740
13814
  }
13741
13815
  }
@@ -13777,18 +13851,37 @@ async function doctorCommand(options = {}) {
13777
13851
  }
13778
13852
 
13779
13853
  // src/cli/init.ts
13780
- import { join as join7 } from "node:path";
13854
+ import { join as join8 } from "node:path";
13781
13855
 
13782
13856
  // src/core/installer.ts
13783
13857
  init_fs();
13784
13858
  import { readFile as fsReadFile, writeFile as fsWriteFile, rename } from "node:fs/promises";
13785
- import { basename as basename2, join as join5 } from "node:path";
13859
+ import { basename as basename2, join as join6 } from "node:path";
13786
13860
 
13787
13861
  // src/core/file-preservation.ts
13788
13862
  init_fs();
13789
13863
  import { basename, join as join4 } from "node:path";
13790
13864
  var DEFAULT_CRITICAL_FILES = ["settings.json", "settings.local.json"];
13791
13865
  var DEFAULT_CRITICAL_DIRECTORIES = ["agent-memory", "agent-memory-local"];
13866
+ var PROTECTED_FRAMEWORK_FILES = ["CLAUDE.md", "AGENTS.md"];
13867
+ var PROTECTED_RULE_PATTERNS = ["rules/MUST-*.md"];
13868
+ function isProtectedFile(relativePath) {
13869
+ const basename2 = relativePath.split("/").pop() ?? "";
13870
+ if (PROTECTED_FRAMEWORK_FILES.includes(basename2)) {
13871
+ return true;
13872
+ }
13873
+ for (const pattern of PROTECTED_RULE_PATTERNS) {
13874
+ if (matchesGlobPattern(relativePath, pattern)) {
13875
+ return true;
13876
+ }
13877
+ }
13878
+ return false;
13879
+ }
13880
+ function matchesGlobPattern(filePath, pattern) {
13881
+ const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*");
13882
+ const regex = new RegExp(`(^|/)${regexStr}$`);
13883
+ return regex.test(filePath);
13884
+ }
13792
13885
  async function extractSingleFile(fileName, rootDir, tempDir, result) {
13793
13886
  const srcPath = join4(rootDir, fileName);
13794
13887
  const destPath = join4(tempDir, fileName);
@@ -14160,11 +14253,139 @@ function getDefaultWorkflow() {
14160
14253
  };
14161
14254
  }
14162
14255
 
14256
+ // src/core/lockfile.ts
14257
+ init_fs();
14258
+ import { createHash } from "node:crypto";
14259
+ import { createReadStream } from "node:fs";
14260
+ import { readdir, stat } from "node:fs/promises";
14261
+ import { join as join5, relative as relative2 } from "node:path";
14262
+ var LOCKFILE_NAME = ".omcustom.lock.json";
14263
+ var LOCKFILE_VERSION = 1;
14264
+ var LOCKFILE_COMPONENTS = [
14265
+ "rules",
14266
+ "agents",
14267
+ "skills",
14268
+ "hooks",
14269
+ "contexts",
14270
+ "ontology",
14271
+ "guides"
14272
+ ];
14273
+ var COMPONENT_PATHS = LOCKFILE_COMPONENTS.map((component) => [getComponentPath(component), component]);
14274
+ function computeFileHash(filePath) {
14275
+ return new Promise((resolve2, reject) => {
14276
+ const hash = createHash("sha256");
14277
+ const stream = createReadStream(filePath);
14278
+ stream.on("error", (err) => {
14279
+ reject(err);
14280
+ });
14281
+ stream.on("data", (chunk) => {
14282
+ hash.update(chunk);
14283
+ });
14284
+ stream.on("end", () => {
14285
+ resolve2(hash.digest("hex"));
14286
+ });
14287
+ });
14288
+ }
14289
+ async function writeLockfile(targetDir, lockfile) {
14290
+ const lockfilePath = join5(targetDir, LOCKFILE_NAME);
14291
+ await writeJsonFile(lockfilePath, lockfile);
14292
+ debug("lockfile.written", { path: lockfilePath });
14293
+ }
14294
+ function resolveComponent(relativePath) {
14295
+ const normalized = relativePath.replace(/\\/g, "/");
14296
+ for (const [prefix, component] of COMPONENT_PATHS) {
14297
+ if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
14298
+ return component;
14299
+ }
14300
+ }
14301
+ return "unknown";
14302
+ }
14303
+ async function collectFiles(dir2, projectRoot, isTopLevel) {
14304
+ const results = [];
14305
+ let entries;
14306
+ try {
14307
+ entries = await readdir(dir2);
14308
+ } catch {
14309
+ return results;
14310
+ }
14311
+ for (const entry of entries) {
14312
+ if (isTopLevel && entry.startsWith(".") && entry !== ".claude") {
14313
+ continue;
14314
+ }
14315
+ const fullPath = join5(dir2, entry);
14316
+ let fileStat;
14317
+ try {
14318
+ fileStat = await stat(fullPath);
14319
+ } catch {
14320
+ continue;
14321
+ }
14322
+ if (fileStat.isDirectory()) {
14323
+ const subFiles = await collectFiles(fullPath, projectRoot, false);
14324
+ results.push(...subFiles);
14325
+ } else if (fileStat.isFile()) {
14326
+ results.push(fullPath);
14327
+ }
14328
+ }
14329
+ return results;
14330
+ }
14331
+ async function generateLockfile(targetDir, generatorVersion, templateVersion) {
14332
+ const files = {};
14333
+ const componentRoots = COMPONENT_PATHS.map(([prefix]) => join5(targetDir, prefix));
14334
+ for (const componentRoot of componentRoots) {
14335
+ const exists2 = await fileExists(componentRoot);
14336
+ if (!exists2) {
14337
+ debug("lockfile.component_dir_missing", { path: componentRoot });
14338
+ continue;
14339
+ }
14340
+ const allFiles = await collectFiles(componentRoot, targetDir, false);
14341
+ for (const absolutePath of allFiles) {
14342
+ const relativePath = relative2(targetDir, absolutePath).replace(/\\/g, "/");
14343
+ let hash;
14344
+ let size;
14345
+ try {
14346
+ hash = await computeFileHash(absolutePath);
14347
+ const fileStat = await stat(absolutePath);
14348
+ size = fileStat.size;
14349
+ } catch (err) {
14350
+ warn("lockfile.hash_failed", { path: absolutePath, error: String(err) });
14351
+ continue;
14352
+ }
14353
+ const component = resolveComponent(relativePath);
14354
+ files[relativePath] = {
14355
+ templateHash: hash,
14356
+ size,
14357
+ component
14358
+ };
14359
+ debug("lockfile.entry_added", { path: relativePath, component });
14360
+ }
14361
+ }
14362
+ return {
14363
+ lockfileVersion: LOCKFILE_VERSION,
14364
+ generatorVersion,
14365
+ generatedAt: new Date().toISOString(),
14366
+ templateVersion,
14367
+ files
14368
+ };
14369
+ }
14370
+ async function generateAndWriteLockfileForDir(targetDir) {
14371
+ try {
14372
+ const packageRoot = getPackageRoot();
14373
+ const manifest = await readJsonFile(join5(packageRoot, "templates", "manifest.json"));
14374
+ const { version: generatorVersion } = await readJsonFile(join5(packageRoot, "package.json"));
14375
+ const lockfile = await generateLockfile(targetDir, generatorVersion, manifest.version);
14376
+ await writeLockfile(targetDir, lockfile);
14377
+ return { fileCount: Object.keys(lockfile.files).length };
14378
+ } catch (err) {
14379
+ const msg = err instanceof Error ? err.message : String(err);
14380
+ return { fileCount: 0, warning: `Lockfile generation failed: ${msg}` };
14381
+ }
14382
+ }
14383
+
14163
14384
  // src/core/installer.ts
14164
14385
  var DEFAULT_LANGUAGE2 = "en";
14165
14386
  function getTemplateDir() {
14166
14387
  const packageRoot = getPackageRoot();
14167
- return join5(packageRoot, "templates");
14388
+ return join6(packageRoot, "templates");
14168
14389
  }
14169
14390
  function createInstallResult(targetDir) {
14170
14391
  return {
@@ -14186,7 +14407,7 @@ async function handleBackup(targetDir, shouldBackup, result) {
14186
14407
  if (!shouldBackup)
14187
14408
  return null;
14188
14409
  const layout = getProviderLayout();
14189
- const rootDir = join5(targetDir, layout.rootDir);
14410
+ const rootDir = join6(targetDir, layout.rootDir);
14190
14411
  let preservation = null;
14191
14412
  if (await fileExists(rootDir)) {
14192
14413
  const { createTempDir: createTempDir2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
@@ -14243,8 +14464,8 @@ async function installSingleComponent(targetDir, component, options, result) {
14243
14464
  }
14244
14465
  async function installStatusline(targetDir, options, _result) {
14245
14466
  const layout = getProviderLayout();
14246
- const srcPath = resolveTemplatePath(join5(layout.rootDir, "statusline.sh"));
14247
- const destPath = join5(targetDir, layout.rootDir, "statusline.sh");
14467
+ const srcPath = resolveTemplatePath(join6(layout.rootDir, "statusline.sh"));
14468
+ const destPath = join6(targetDir, layout.rootDir, "statusline.sh");
14248
14469
  if (!await fileExists(srcPath)) {
14249
14470
  debug("install.statusline_not_found", { path: srcPath });
14250
14471
  return;
@@ -14262,7 +14483,7 @@ async function installStatusline(targetDir, options, _result) {
14262
14483
  }
14263
14484
  async function installSettingsLocal(targetDir, result) {
14264
14485
  const layout = getProviderLayout();
14265
- const settingsPath = join5(targetDir, layout.rootDir, "settings.local.json");
14486
+ const settingsPath = join6(targetDir, layout.rootDir, "settings.local.json");
14266
14487
  const statusLineConfig = {
14267
14488
  statusLine: {
14268
14489
  type: "command",
@@ -14319,7 +14540,7 @@ async function install(options) {
14319
14540
  await installEntryDocWithTracking(options.targetDir, options, result);
14320
14541
  if (preservation) {
14321
14542
  const layout = getProviderLayout();
14322
- const rootDir = join5(options.targetDir, layout.rootDir);
14543
+ const rootDir = join6(options.targetDir, layout.rootDir);
14323
14544
  const restoration = await restoreCriticalFiles(rootDir, preservation);
14324
14545
  if (restoration.restoredFiles.length > 0 || restoration.restoredDirs.length > 0) {
14325
14546
  info("install.restored", {
@@ -14335,6 +14556,13 @@ async function install(options) {
14335
14556
  await cleanupPreservation(preservation.tempDir);
14336
14557
  }
14337
14558
  await updateInstallConfig(options.targetDir, options, result.installedComponents);
14559
+ const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir);
14560
+ if (lockfileResult.warning) {
14561
+ result.warnings.push(lockfileResult.warning);
14562
+ warn("install.lockfile_failed", { error: lockfileResult.warning });
14563
+ } else {
14564
+ info("install.lockfile_generated", { files: String(lockfileResult.fileCount) });
14565
+ }
14338
14566
  result.success = true;
14339
14567
  success("install.success");
14340
14568
  } catch (err) {
@@ -14352,7 +14580,7 @@ async function installComponent(targetDir, component, options) {
14352
14580
  return false;
14353
14581
  }
14354
14582
  const templatePath = getComponentPath(component);
14355
- const destPath = join5(targetDir, templatePath);
14583
+ const destPath = join6(targetDir, templatePath);
14356
14584
  const destExists = await fileExists(destPath);
14357
14585
  if (destExists && !options.force && !options.backup) {
14358
14586
  debug("install.component_skipped", { component });
@@ -14380,7 +14608,7 @@ async function installEntryDoc(targetDir, language, overwrite = false) {
14380
14608
  const layout = getProviderLayout();
14381
14609
  const templateFile = getEntryTemplateName(language);
14382
14610
  const srcPath = resolveTemplatePath(templateFile);
14383
- const destPath = join5(targetDir, layout.entryFile);
14611
+ const destPath = join6(targetDir, layout.entryFile);
14384
14612
  if (!await fileExists(srcPath)) {
14385
14613
  warn("install.entry_md_not_found", { language, path: srcPath, entry: layout.entryFile });
14386
14614
  return false;
@@ -14401,7 +14629,7 @@ async function installEntryDoc(targetDir, language, overwrite = false) {
14401
14629
  }
14402
14630
  async function backupExisting(sourcePath, backupDir) {
14403
14631
  const name = basename2(sourcePath);
14404
- const backupPath = join5(backupDir, name);
14632
+ const backupPath = join6(backupDir, name);
14405
14633
  await rename(sourcePath, backupPath);
14406
14634
  return backupPath;
14407
14635
  }
@@ -14410,7 +14638,7 @@ async function checkExistingPaths(targetDir) {
14410
14638
  const pathsToCheck = [layout.entryFile, layout.rootDir, "guides"];
14411
14639
  const existingPaths = [];
14412
14640
  for (const relativePath of pathsToCheck) {
14413
- const fullPath = join5(targetDir, relativePath);
14641
+ const fullPath = join6(targetDir, relativePath);
14414
14642
  if (await fileExists(fullPath)) {
14415
14643
  existingPaths.push(relativePath);
14416
14644
  }
@@ -14424,11 +14652,11 @@ async function backupExistingInstallation(targetDir) {
14424
14652
  return [];
14425
14653
  }
14426
14654
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
14427
- const backupDir = join5(targetDir, `${layout.backupDirPrefix}${timestamp}`);
14655
+ const backupDir = join6(targetDir, `${layout.backupDirPrefix}${timestamp}`);
14428
14656
  await ensureDirectory(backupDir);
14429
14657
  const backedUpPaths = [];
14430
14658
  for (const relativePath of existingPaths) {
14431
- const fullPath = join5(targetDir, relativePath);
14659
+ const fullPath = join6(targetDir, relativePath);
14432
14660
  try {
14433
14661
  const backupPath = await backupExisting(fullPath, backupDir);
14434
14662
  backedUpPaths.push(backupPath);
@@ -14445,12 +14673,12 @@ async function backupExistingInstallation(targetDir) {
14445
14673
  init_fs();
14446
14674
  import { execSync as execSync3 } from "node:child_process";
14447
14675
  import { writeFile } from "node:fs/promises";
14448
- import { join as join6 } from "node:path";
14676
+ import { join as join7 } from "node:path";
14449
14677
  async function generateMCPConfig(targetDir) {
14450
14678
  const layout = getProviderLayout();
14451
- const mcpConfigPath = join6(targetDir, ".mcp.json");
14452
- const ontologyDir = join6(layout.rootDir, "ontology");
14453
- const ontologyExists = await fileExists(join6(targetDir, ontologyDir));
14679
+ const mcpConfigPath = join7(targetDir, ".mcp.json");
14680
+ const ontologyDir = join7(layout.rootDir, "ontology");
14681
+ const ontologyExists = await fileExists(join7(targetDir, ontologyDir));
14454
14682
  if (!ontologyExists) {
14455
14683
  return;
14456
14684
  }
@@ -14507,7 +14735,7 @@ async function checkUvAvailable() {
14507
14735
  init_fs();
14508
14736
  async function checkExistingInstallation(targetDir) {
14509
14737
  const layout = getProviderLayout();
14510
- const rootDir = join7(targetDir, layout.rootDir);
14738
+ const rootDir = join8(targetDir, layout.rootDir);
14511
14739
  return fileExists(rootDir);
14512
14740
  }
14513
14741
  var PROVIDER_SUBDIR_COMPONENTS = new Set([
@@ -14521,13 +14749,13 @@ var PROVIDER_SUBDIR_COMPONENTS = new Set([
14521
14749
  function componentToPath(targetDir, component) {
14522
14750
  if (component === "entry-md") {
14523
14751
  const layout = getProviderLayout();
14524
- return join7(targetDir, layout.entryFile);
14752
+ return join8(targetDir, layout.entryFile);
14525
14753
  }
14526
14754
  if (PROVIDER_SUBDIR_COMPONENTS.has(component)) {
14527
14755
  const layout = getProviderLayout();
14528
- return join7(targetDir, layout.rootDir, component);
14756
+ return join8(targetDir, layout.rootDir, component);
14529
14757
  }
14530
- return join7(targetDir, component);
14758
+ return join8(targetDir, component);
14531
14759
  }
14532
14760
  function buildInstalledPaths(targetDir, components) {
14533
14761
  return components.map((component) => componentToPath(targetDir, component));
@@ -14607,7 +14835,7 @@ async function initCommand(options) {
14607
14835
  }
14608
14836
 
14609
14837
  // src/cli/list.ts
14610
- import { basename as basename3, dirname as dirname3, join as join8, relative as relative2 } from "node:path";
14838
+ import { basename as basename3, dirname as dirname3, join as join9, relative as relative3 } from "node:path";
14611
14839
  init_fs();
14612
14840
  var ALLOWED_TOP_LEVEL_KEYS = new Set(["name", "type", "description", "version", "category"]);
14613
14841
  function parseKeyValue(line) {
@@ -14672,12 +14900,12 @@ function extractAgentTypeFromFilename(filename) {
14672
14900
  return prefixMap[prefix] || "unknown";
14673
14901
  }
14674
14902
  function extractSkillCategoryFromPath(skillPath, baseDir, rootDir) {
14675
- const relativePath = relative2(join8(baseDir, rootDir, "skills"), skillPath);
14903
+ const relativePath = relative3(join9(baseDir, rootDir, "skills"), skillPath);
14676
14904
  const parts = relativePath.split("/").filter(Boolean);
14677
14905
  return parts[0] || "unknown";
14678
14906
  }
14679
14907
  function extractGuideCategoryFromPath(guidePath, baseDir) {
14680
- const relativePath = relative2(join8(baseDir, "guides"), guidePath);
14908
+ const relativePath = relative3(join9(baseDir, "guides"), guidePath);
14681
14909
  const parts = relativePath.split("/").filter(Boolean);
14682
14910
  return parts[0] || "unknown";
14683
14911
  }
@@ -14771,7 +14999,7 @@ async function tryExtractMarkdownDescription(mdPath, options = {}) {
14771
14999
  }
14772
15000
  }
14773
15001
  async function getAgents(targetDir, rootDir = ".claude", config) {
14774
- const agentsDir = join8(targetDir, rootDir, "agents");
15002
+ const agentsDir = join9(targetDir, rootDir, "agents");
14775
15003
  if (!await fileExists(agentsDir))
14776
15004
  return [];
14777
15005
  try {
@@ -14783,7 +15011,7 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
14783
15011
  const filename = basename3(agentMdPath);
14784
15012
  const name = basename3(filename, ".md");
14785
15013
  const description = await tryExtractMarkdownDescription(agentMdPath);
14786
- const relativePath = relative2(targetDir, agentMdPath);
15014
+ const relativePath = relative3(targetDir, agentMdPath);
14787
15015
  return {
14788
15016
  name,
14789
15017
  type: extractAgentTypeFromFilename(filename),
@@ -14799,7 +15027,7 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
14799
15027
  }
14800
15028
  }
14801
15029
  async function getSkills(targetDir, rootDir = ".claude", config) {
14802
- const skillsDir = join8(targetDir, rootDir, "skills");
15030
+ const skillsDir = join9(targetDir, rootDir, "skills");
14803
15031
  if (!await fileExists(skillsDir))
14804
15032
  return [];
14805
15033
  try {
@@ -14809,9 +15037,9 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
14809
15037
  const skillMdFiles = await listFiles(skillsDir, { recursive: true, pattern: "SKILL.md" });
14810
15038
  const skills = await Promise.all(skillMdFiles.map(async (skillMdPath) => {
14811
15039
  const skillDir = dirname3(skillMdPath);
14812
- const indexYamlPath = join8(skillDir, "index.yaml");
15040
+ const indexYamlPath = join9(skillDir, "index.yaml");
14813
15041
  const { description, version } = await tryReadIndexYamlMetadata(indexYamlPath);
14814
- const relativePath = relative2(targetDir, skillDir);
15042
+ const relativePath = relative3(targetDir, skillDir);
14815
15043
  return {
14816
15044
  name: basename3(skillDir),
14817
15045
  type: "skill",
@@ -14828,7 +15056,7 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
14828
15056
  }
14829
15057
  }
14830
15058
  async function getGuides(targetDir, config) {
14831
- const guidesDir = join8(targetDir, "guides");
15059
+ const guidesDir = join9(targetDir, "guides");
14832
15060
  if (!await fileExists(guidesDir))
14833
15061
  return [];
14834
15062
  try {
@@ -14838,7 +15066,7 @@ async function getGuides(targetDir, config) {
14838
15066
  const guideMdFiles = await listFiles(guidesDir, { recursive: true, pattern: "*.md" });
14839
15067
  const guides = await Promise.all(guideMdFiles.map(async (guideMdPath) => {
14840
15068
  const description = await tryExtractMarkdownDescription(guideMdPath, { maxLength: 100 });
14841
- const relativePath = relative2(targetDir, guideMdPath);
15069
+ const relativePath = relative3(targetDir, guideMdPath);
14842
15070
  return {
14843
15071
  name: basename3(guideMdPath, ".md"),
14844
15072
  type: "guide",
@@ -14855,7 +15083,7 @@ async function getGuides(targetDir, config) {
14855
15083
  }
14856
15084
  var RULE_PRIORITY_ORDER = { MUST: 0, SHOULD: 1, MAY: 2 };
14857
15085
  async function getRules(targetDir, rootDir = ".claude", config) {
14858
- const rulesDir = join8(targetDir, rootDir, "rules");
15086
+ const rulesDir = join9(targetDir, rootDir, "rules");
14859
15087
  if (!await fileExists(rulesDir))
14860
15088
  return [];
14861
15089
  try {
@@ -14868,7 +15096,7 @@ async function getRules(targetDir, rootDir = ".claude", config) {
14868
15096
  const description = await tryExtractMarkdownDescription(ruleMdPath, {
14869
15097
  cleanFormatting: true
14870
15098
  });
14871
- const relativePath = relative2(targetDir, ruleMdPath);
15099
+ const relativePath = relative3(targetDir, ruleMdPath);
14872
15100
  return {
14873
15101
  name: basename3(ruleMdPath, ".md"),
14874
15102
  type: extractRulePriorityFromFilename(filename),
@@ -14927,7 +15155,7 @@ function formatAsJson(components) {
14927
15155
  console.log(JSON.stringify(components, null, 2));
14928
15156
  }
14929
15157
  async function getHooks(targetDir, rootDir = ".claude") {
14930
- const hooksDir = join8(targetDir, rootDir, "hooks");
15158
+ const hooksDir = join9(targetDir, rootDir, "hooks");
14931
15159
  if (!await fileExists(hooksDir))
14932
15160
  return [];
14933
15161
  try {
@@ -14938,14 +15166,14 @@ async function getHooks(targetDir, rootDir = ".claude") {
14938
15166
  return allFiles.map((hookPath) => ({
14939
15167
  name: basename3(hookPath),
14940
15168
  type: "hook",
14941
- path: relative2(targetDir, hookPath)
15169
+ path: relative3(targetDir, hookPath)
14942
15170
  })).sort((a, b) => a.name.localeCompare(b.name));
14943
15171
  } catch {
14944
15172
  return [];
14945
15173
  }
14946
15174
  }
14947
15175
  async function getContexts(targetDir, rootDir = ".claude") {
14948
- const contextsDir = join8(targetDir, rootDir, "contexts");
15176
+ const contextsDir = join9(targetDir, rootDir, "contexts");
14949
15177
  if (!await fileExists(contextsDir))
14950
15178
  return [];
14951
15179
  try {
@@ -14958,7 +15186,7 @@ async function getContexts(targetDir, rootDir = ".claude") {
14958
15186
  return {
14959
15187
  name: basename3(ctxPath, ext),
14960
15188
  type: "context",
14961
- path: relative2(targetDir, ctxPath),
15189
+ path: relative3(targetDir, ctxPath),
14962
15190
  description
14963
15191
  };
14964
15192
  }));
@@ -15335,7 +15563,7 @@ async function securityCommand(_options = {}) {
15335
15563
 
15336
15564
  // src/core/updater.ts
15337
15565
  init_fs();
15338
- import { join as join9 } from "node:path";
15566
+ import { join as join10 } from "node:path";
15339
15567
 
15340
15568
  // src/core/entry-merger.ts
15341
15569
  var MANAGED_START = "<!-- omcustom:start -->";
@@ -15584,7 +15812,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
15584
15812
  }
15585
15813
  async function updateEntryDoc(targetDir, config, options) {
15586
15814
  const layout = getProviderLayout();
15587
- const entryPath = join9(targetDir, layout.entryFile);
15815
+ const entryPath = join10(targetDir, layout.entryFile);
15588
15816
  const templateName = getEntryTemplateName2(config.language);
15589
15817
  const templatePath = resolveTemplatePath(templateName);
15590
15818
  if (!await fileExists(templatePath)) {
@@ -15664,6 +15892,15 @@ async function update(options) {
15664
15892
  const components = options.components || getAllUpdateComponents();
15665
15893
  await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config);
15666
15894
  await runFullUpdatePostProcessing(options, result, config);
15895
+ const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir);
15896
+ if (lockfileResult.warning) {
15897
+ result.warnings.push(lockfileResult.warning);
15898
+ warn("update.lockfile_failed", { error: lockfileResult.warning });
15899
+ } else {
15900
+ debug("update.lockfile_regenerated", {
15901
+ files: String(lockfileResult.fileCount)
15902
+ });
15903
+ }
15667
15904
  } catch (err) {
15668
15905
  const message = err instanceof Error ? err.message : String(err);
15669
15906
  result.error = message;
@@ -15714,11 +15951,55 @@ async function componentHasUpdate(_targetDir, component, config) {
15714
15951
  const latestVersion = await getLatestVersion();
15715
15952
  return installedVersion !== latestVersion;
15716
15953
  }
15954
+ async function collectProtectedSkipPaths(srcPath, destPath, componentPath, forceOverwriteAll) {
15955
+ if (forceOverwriteAll) {
15956
+ const warnedPaths = await findProtectedFilesInDir(srcPath, componentPath);
15957
+ return { skipPaths: [], warnedPaths };
15958
+ }
15959
+ const protectedRelative = await findProtectedFilesInDir(srcPath, componentPath);
15960
+ const path3 = await import("node:path");
15961
+ const skipPaths = protectedRelative.map((p) => path3.relative(destPath, join10(destPath, p)));
15962
+ return { skipPaths, warnedPaths: protectedRelative };
15963
+ }
15964
+ function isEntryProtected(relPath, componentRelativePrefix) {
15965
+ if (isProtectedFile(relPath)) {
15966
+ return true;
15967
+ }
15968
+ const componentPrefixed = componentRelativePrefix ? `${componentRelativePrefix}/${relPath}` : relPath;
15969
+ return isProtectedFile(componentPrefixed);
15970
+ }
15971
+ async function safeReaddir(dir2, fs3) {
15972
+ try {
15973
+ return await fs3.readdir(dir2, { withFileTypes: true });
15974
+ } catch {
15975
+ return [];
15976
+ }
15977
+ }
15978
+ async function findProtectedFilesInDir(dirPath, componentRelativePrefix) {
15979
+ const fs3 = await import("node:fs/promises");
15980
+ const path3 = await import("node:path");
15981
+ const protected_ = [];
15982
+ const queue = [{ dir: dirPath, relDir: "" }];
15983
+ while (queue.length > 0) {
15984
+ const { dir: dir2, relDir } = queue.shift();
15985
+ const entries = await safeReaddir(dir2, fs3);
15986
+ for (const entry of entries) {
15987
+ const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
15988
+ const fullPath = path3.join(dir2, entry.name);
15989
+ if (entry.isDirectory()) {
15990
+ queue.push({ dir: fullPath, relDir: relPath });
15991
+ } else if (entry.isFile() && isEntryProtected(relPath, componentRelativePrefix)) {
15992
+ protected_.push(relPath);
15993
+ }
15994
+ }
15995
+ }
15996
+ return protected_;
15997
+ }
15717
15998
  async function updateComponent(targetDir, component, customizations, options, config) {
15718
15999
  const preservedFiles = [];
15719
16000
  const componentPath = getComponentPath2(component);
15720
16001
  const srcPath = resolveTemplatePath(componentPath);
15721
- const destPath = join9(targetDir, componentPath);
16002
+ const destPath = join10(targetDir, componentPath);
15722
16003
  const customComponents = config.customComponents || [];
15723
16004
  const skipPaths = [];
15724
16005
  if (customizations && !options.forceOverwriteAll) {
@@ -15731,15 +16012,34 @@ async function updateComponent(targetDir, component, customizations, options, co
15731
16012
  skipPaths.push(cc.path);
15732
16013
  }
15733
16014
  }
16015
+ const { skipPaths: protectedSkipPaths, warnedPaths: protectedWarnedPaths } = await collectProtectedSkipPaths(srcPath, destPath, componentPath, !!options.forceOverwriteAll);
16016
+ for (const protectedPath of protectedWarnedPaths) {
16017
+ if (options.forceOverwriteAll) {
16018
+ warn("update.protected_file_force_overwrite", {
16019
+ file: protectedPath,
16020
+ component,
16021
+ hint: "File contains AI behavioral constraints. Overwriting because --force-overwrite-all was set."
16022
+ });
16023
+ } else {
16024
+ warn("update.protected_file_skipped", {
16025
+ file: protectedPath,
16026
+ component,
16027
+ hint: "File contains AI behavioral constraints and was not updated. Use --force-overwrite-all to override."
16028
+ });
16029
+ }
16030
+ }
16031
+ skipPaths.push(...protectedSkipPaths);
15734
16032
  const path3 = await import("node:path");
15735
- const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join9(targetDir, p)));
16033
+ const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join10(targetDir, p)));
16034
+ const uniqueSkipPaths = [...new Set(normalizedSkipPaths)];
15736
16035
  await copyDirectory(srcPath, destPath, {
15737
16036
  overwrite: true,
15738
- skipPaths: normalizedSkipPaths.length > 0 ? normalizedSkipPaths : undefined
16037
+ skipPaths: uniqueSkipPaths.length > 0 ? uniqueSkipPaths : undefined
15739
16038
  });
15740
16039
  debug("update.component_updated", {
15741
16040
  component,
15742
- skippedPaths: String(normalizedSkipPaths.length)
16041
+ skippedPaths: String(uniqueSkipPaths.length),
16042
+ protectedSkipped: String(protectedSkipPaths.length)
15743
16043
  });
15744
16044
  return preservedFiles;
15745
16045
  }
@@ -15752,12 +16052,12 @@ async function syncRootLevelFiles(targetDir, options) {
15752
16052
  const layout = getProviderLayout();
15753
16053
  const synced = [];
15754
16054
  for (const fileName of ROOT_LEVEL_FILES) {
15755
- const srcPath = resolveTemplatePath(join9(layout.rootDir, fileName));
16055
+ const srcPath = resolveTemplatePath(join10(layout.rootDir, fileName));
15756
16056
  if (!await fileExists(srcPath)) {
15757
16057
  continue;
15758
16058
  }
15759
- const destPath = join9(targetDir, layout.rootDir, fileName);
15760
- await ensureDirectory(join9(destPath, ".."));
16059
+ const destPath = join10(targetDir, layout.rootDir, fileName);
16060
+ await ensureDirectory(join10(destPath, ".."));
15761
16061
  await fs3.copyFile(srcPath, destPath);
15762
16062
  if (fileName.endsWith(".sh")) {
15763
16063
  await fs3.chmod(destPath, 493);
@@ -15792,7 +16092,7 @@ async function removeDeprecatedFiles(targetDir, options) {
15792
16092
  });
15793
16093
  continue;
15794
16094
  }
15795
- const fullPath = join9(targetDir, entry.path);
16095
+ const fullPath = join10(targetDir, entry.path);
15796
16096
  if (await fileExists(fullPath)) {
15797
16097
  await fs3.unlink(fullPath);
15798
16098
  removed.push(entry.path);
@@ -15816,26 +16116,26 @@ function getComponentPath2(component) {
15816
16116
  }
15817
16117
  async function backupInstallation(targetDir) {
15818
16118
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
15819
- const backupDir = join9(targetDir, `.omcustom-backup-${timestamp}`);
16119
+ const backupDir = join10(targetDir, `.omcustom-backup-${timestamp}`);
15820
16120
  const fs3 = await import("node:fs/promises");
15821
16121
  await ensureDirectory(backupDir);
15822
16122
  const layout = getProviderLayout();
15823
16123
  const dirsToBackup = [layout.rootDir, "guides"];
15824
16124
  for (const dir2 of dirsToBackup) {
15825
- const srcPath = join9(targetDir, dir2);
16125
+ const srcPath = join10(targetDir, dir2);
15826
16126
  if (await fileExists(srcPath)) {
15827
- const destPath = join9(backupDir, dir2);
16127
+ const destPath = join10(backupDir, dir2);
15828
16128
  await copyDirectory(srcPath, destPath, { overwrite: true });
15829
16129
  }
15830
16130
  }
15831
- const entryPath = join9(targetDir, layout.entryFile);
16131
+ const entryPath = join10(targetDir, layout.entryFile);
15832
16132
  if (await fileExists(entryPath)) {
15833
- await fs3.copyFile(entryPath, join9(backupDir, layout.entryFile));
16133
+ await fs3.copyFile(entryPath, join10(backupDir, layout.entryFile));
15834
16134
  }
15835
16135
  return backupDir;
15836
16136
  }
15837
16137
  async function loadCustomizationManifest(targetDir) {
15838
- const manifestPath = join9(targetDir, CUSTOMIZATION_MANIFEST_FILE);
16138
+ const manifestPath = join10(targetDir, CUSTOMIZATION_MANIFEST_FILE);
15839
16139
  if (await fileExists(manifestPath)) {
15840
16140
  return readJsonFile(manifestPath);
15841
16141
  }
@@ -15938,7 +16238,7 @@ function createProgram() {
15938
16238
  verbose: options.verbose
15939
16239
  });
15940
16240
  });
15941
- program2.command("doctor").description(i18n.t("cli.doctor.description")).option("--fix", i18n.t("cli.doctor.fixOption")).action(async (options) => {
16241
+ program2.command("doctor").description(i18n.t("cli.doctor.description")).option("--fix", i18n.t("cli.doctor.fixOption")).option("--updates", i18n.t("cli.doctor.updatesOption")).action(async (options) => {
15942
16242
  await doctorCommand(options);
15943
16243
  });
15944
16244
  program2.command("security").description(i18n.t("cli.security.description")).option("--verbose", i18n.t("cli.security.verboseOption")).action(async (options) => {