oh-my-customcode 0.24.2 → 0.30.1

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/index.js CHANGED
@@ -1,10 +1,47 @@
1
1
  import { createRequire } from "node:module";
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
2
17
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
18
 
4
- // src/core/config.ts
5
- import { join as join2 } from "node:path";
6
-
7
19
  // src/utils/fs.ts
20
+ var exports_fs = {};
21
+ __export(exports_fs, {
22
+ writeTextFile: () => writeTextFile,
23
+ writeJsonFile: () => writeJsonFile,
24
+ validatePreserveFilePath: () => validatePreserveFilePath,
25
+ resolveTemplatePath: () => resolveTemplatePath,
26
+ resolvePath: () => resolvePath,
27
+ remove: () => remove,
28
+ readTextFile: () => readTextFile,
29
+ readJsonFile: () => readJsonFile,
30
+ normalizePath: () => normalizePath,
31
+ move: () => move,
32
+ listFiles: () => listFiles,
33
+ isAbsolutePath: () => isAbsolutePath,
34
+ getRelativePath: () => getRelativePath,
35
+ getPackageRoot: () => getPackageRoot,
36
+ getFileStats: () => getFileStats,
37
+ filesAreIdentical: () => filesAreIdentical,
38
+ fileExists: () => fileExists,
39
+ ensureDirectory: () => ensureDirectory,
40
+ createTempDir: () => createTempDir,
41
+ copyFile: () => copyFile,
42
+ copyDirectory: () => copyDirectory,
43
+ calculateChecksum: () => calculateChecksum
44
+ });
8
45
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
46
  import { fileURLToPath } from "node:url";
10
47
  function validatePreserveFilePath(filePath, projectRoot) {
@@ -154,6 +191,15 @@ async function writeTextFile(path, content) {
154
191
  await ensureDirectory(dirname(path));
155
192
  await fs.writeFile(path, content, "utf-8");
156
193
  }
194
+ async function remove(path) {
195
+ const fs = await import("node:fs/promises");
196
+ const stat = await fs.stat(path);
197
+ if (stat.isDirectory()) {
198
+ await fs.rm(path, { recursive: true, force: true });
199
+ } else {
200
+ await fs.unlink(path);
201
+ }
202
+ }
157
203
  function getPackageRoot() {
158
204
  const currentFile = fileURLToPath(import.meta.url);
159
205
  const currentDir = dirname(currentFile);
@@ -163,16 +209,91 @@ function resolveTemplatePath(relativePath) {
163
209
  const packageRoot = getPackageRoot();
164
210
  return join(packageRoot, "templates", relativePath);
165
211
  }
212
+ async function listFiles(dir, options = {}) {
213
+ const fs = await import("node:fs/promises");
214
+ const path = await import("node:path");
215
+ const files = [];
216
+ const entries = await fs.readdir(dir, { withFileTypes: true });
217
+ for (const entry of entries) {
218
+ const fullPath = path.join(dir, entry.name);
219
+ if (entry.isDirectory() && options.recursive) {
220
+ const subFiles = await listFiles(fullPath, options);
221
+ files.push(...subFiles);
222
+ } else if (entry.isFile()) {
223
+ if (!options.pattern || matchesPattern(entry.name, options.pattern)) {
224
+ files.push(fullPath);
225
+ }
226
+ }
227
+ }
228
+ return files;
229
+ }
230
+ async function getFileStats(path) {
231
+ const fs = await import("node:fs/promises");
232
+ const stats = await fs.stat(path);
233
+ return {
234
+ size: stats.size,
235
+ created: stats.birthtime,
236
+ modified: stats.mtime,
237
+ isDirectory: stats.isDirectory(),
238
+ isFile: stats.isFile()
239
+ };
240
+ }
166
241
  async function copyFile(src, dest) {
167
242
  const fs = await import("node:fs/promises");
168
243
  await ensureDirectory(dirname(dest));
169
244
  await fs.copyFile(src, dest);
170
245
  }
246
+ async function move(src, dest) {
247
+ const fs = await import("node:fs/promises");
248
+ await ensureDirectory(dirname(dest));
249
+ await fs.rename(src, dest);
250
+ }
251
+ async function createTempDir(prefix = "omcustom-") {
252
+ const fs = await import("node:fs/promises");
253
+ const os = await import("node:os");
254
+ const path = await import("node:path");
255
+ const tempBase = os.tmpdir();
256
+ const tempDir = path.join(tempBase, `${prefix}${Date.now()}`);
257
+ await fs.mkdir(tempDir, { recursive: true });
258
+ return tempDir;
259
+ }
260
+ async function calculateChecksum(path) {
261
+ const fs = await import("node:fs/promises");
262
+ const crypto = await import("node:crypto");
263
+ const content = await fs.readFile(path);
264
+ const hash = crypto.createHash("md5");
265
+ hash.update(content);
266
+ return hash.digest("hex");
267
+ }
268
+ async function filesAreIdentical(path1, path2) {
269
+ const [checksum1, checksum2] = await Promise.all([
270
+ calculateChecksum(path1),
271
+ calculateChecksum(path2)
272
+ ]);
273
+ return checksum1 === checksum2;
274
+ }
171
275
  function matchesPattern(filename, pattern) {
172
276
  const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
173
277
  const regex = new RegExp(`^${regexPattern}$`);
174
278
  return regex.test(filename);
175
279
  }
280
+ function getRelativePath(basePath, fullPath) {
281
+ return relative(basePath, fullPath);
282
+ }
283
+ function normalizePath(inputPath) {
284
+ return inputPath.replace(/\\/g, "/");
285
+ }
286
+ function isAbsolutePath(inputPath) {
287
+ return isAbsolute(inputPath);
288
+ }
289
+ function resolvePath(...paths) {
290
+ return resolve(...paths);
291
+ }
292
+ var init_fs = () => {};
293
+
294
+ // src/core/config.ts
295
+ init_fs();
296
+ import { join as join2 } from "node:path";
176
297
 
177
298
  // src/utils/logger.ts
178
299
  var currentOptions = {
@@ -393,7 +514,12 @@ function getDefaultConfig() {
393
514
  checkIntervalHours: 24,
394
515
  autoApplyMinor: false
395
516
  },
396
- preserveFiles: [],
517
+ preserveFiles: [
518
+ ".claude/settings.json",
519
+ ".claude/settings.local.json",
520
+ ".claude/agent-memory/",
521
+ ".claude/agent-memory-local/"
522
+ ],
397
523
  customComponents: []
398
524
  };
399
525
  }
@@ -748,8 +874,138 @@ function getDefaultWorkflow() {
748
874
  };
749
875
  }
750
876
  // src/core/installer.ts
877
+ init_fs();
751
878
  import { readFile as fsReadFile, writeFile as fsWriteFile, rename } from "node:fs/promises";
879
+ import { basename as basename2, join as join4 } from "node:path";
880
+
881
+ // src/core/file-preservation.ts
882
+ init_fs();
752
883
  import { basename, join as join3 } from "node:path";
884
+ var DEFAULT_CRITICAL_FILES = ["settings.json", "settings.local.json"];
885
+ var DEFAULT_CRITICAL_DIRECTORIES = ["agent-memory", "agent-memory-local"];
886
+ async function extractSingleFile(fileName, rootDir, tempDir, result) {
887
+ const srcPath = join3(rootDir, fileName);
888
+ const destPath = join3(tempDir, fileName);
889
+ try {
890
+ if (await fileExists(srcPath)) {
891
+ await copyFile(srcPath, destPath);
892
+ result.extractedFiles.push(fileName);
893
+ debug("preserve.extracted_file", { file: fileName });
894
+ }
895
+ } catch (err) {
896
+ const reason = err instanceof Error ? err.message : String(err);
897
+ result.failures.push({ path: fileName, reason });
898
+ warn("preserve.extract_failed", { file: fileName, error: reason });
899
+ }
900
+ }
901
+ async function extractSingleDir(dirName, rootDir, tempDir, result) {
902
+ const srcPath = join3(rootDir, dirName);
903
+ const destPath = join3(tempDir, dirName);
904
+ try {
905
+ if (await fileExists(srcPath)) {
906
+ await copyDirectory(srcPath, destPath, { overwrite: true, preserveTimestamps: true });
907
+ result.extractedDirs.push(dirName);
908
+ debug("preserve.extracted_dir", { dir: dirName });
909
+ }
910
+ } catch (err) {
911
+ const reason = err instanceof Error ? err.message : String(err);
912
+ result.failures.push({ path: dirName, reason });
913
+ warn("preserve.extract_dir_failed", { dir: dirName, error: reason });
914
+ }
915
+ }
916
+ async function extractCriticalFiles(rootDir, tempDir, additionalFiles = []) {
917
+ const result = {
918
+ tempDir,
919
+ extractedFiles: [],
920
+ extractedDirs: [],
921
+ failures: []
922
+ };
923
+ await ensureDirectory(tempDir);
924
+ const filesToExtract = [...DEFAULT_CRITICAL_FILES, ...additionalFiles];
925
+ for (const fileName of filesToExtract) {
926
+ await extractSingleFile(fileName, rootDir, tempDir, result);
927
+ }
928
+ for (const dirName of DEFAULT_CRITICAL_DIRECTORIES) {
929
+ await extractSingleDir(dirName, rootDir, tempDir, result);
930
+ }
931
+ return result;
932
+ }
933
+ async function restoreCriticalFiles(rootDir, preservation) {
934
+ const result = {
935
+ restoredFiles: [],
936
+ restoredDirs: [],
937
+ failures: []
938
+ };
939
+ for (const fileName of preservation.extractedFiles) {
940
+ const preservedPath = join3(preservation.tempDir, fileName);
941
+ const targetPath = join3(rootDir, fileName);
942
+ try {
943
+ if (fileName.endsWith(".json")) {
944
+ await mergeJsonFile(preservedPath, targetPath);
945
+ } else {
946
+ await copyFile(preservedPath, targetPath);
947
+ }
948
+ result.restoredFiles.push(fileName);
949
+ debug("preserve.restored_file", { file: fileName });
950
+ } catch (err) {
951
+ const reason = err instanceof Error ? err.message : String(err);
952
+ result.failures.push({ path: fileName, reason });
953
+ warn("preserve.restore_failed", { file: fileName, error: reason });
954
+ }
955
+ }
956
+ for (const dirName of preservation.extractedDirs) {
957
+ const preservedPath = join3(preservation.tempDir, dirName);
958
+ const targetPath = join3(rootDir, dirName);
959
+ try {
960
+ await copyDirectory(preservedPath, targetPath, {
961
+ overwrite: false,
962
+ preserveTimestamps: true
963
+ });
964
+ result.restoredDirs.push(dirName);
965
+ debug("preserve.restored_dir", { dir: dirName });
966
+ } catch (err) {
967
+ const reason = err instanceof Error ? err.message : String(err);
968
+ result.failures.push({ path: dirName, reason });
969
+ warn("preserve.restore_dir_failed", { dir: dirName, error: reason });
970
+ }
971
+ }
972
+ return result;
973
+ }
974
+ async function mergeJsonFile(preservedPath, targetPath) {
975
+ const preservedData = await readJsonFile(preservedPath);
976
+ if (await fileExists(targetPath)) {
977
+ const targetData = await readJsonFile(targetPath);
978
+ const merged = deepMerge(targetData, preservedData);
979
+ await writeJsonFile(targetPath, merged);
980
+ debug("preserve.merged_json", { file: basename(targetPath) });
981
+ } else {
982
+ await copyFile(preservedPath, targetPath);
983
+ debug("preserve.copied_json", { file: basename(targetPath) });
984
+ }
985
+ }
986
+ function deepMerge(target, source) {
987
+ const result = { ...target };
988
+ for (const key of Object.keys(source)) {
989
+ const sourceVal = source[key];
990
+ const targetVal = result[key];
991
+ if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
992
+ result[key] = deepMerge(targetVal, sourceVal);
993
+ } else {
994
+ result[key] = sourceVal;
995
+ }
996
+ }
997
+ return result;
998
+ }
999
+ async function cleanupPreservation(tempDir) {
1000
+ try {
1001
+ const { rm } = await import("node:fs/promises");
1002
+ await rm(tempDir, { recursive: true, force: true });
1003
+ debug("preserve.cleanup", { dir: tempDir });
1004
+ } catch (err) {
1005
+ const reason = err instanceof Error ? err.message : String(err);
1006
+ warn("preserve.cleanup_failed", { dir: tempDir, error: reason });
1007
+ }
1008
+ }
753
1009
 
754
1010
  // src/core/layout.ts
755
1011
  var CLAUDE_LAYOUT = {
@@ -789,7 +1045,7 @@ function getComponentPath(component) {
789
1045
  var DEFAULT_LANGUAGE = "en";
790
1046
  function getTemplateDir() {
791
1047
  const packageRoot = getPackageRoot();
792
- return join3(packageRoot, "templates");
1048
+ return join4(packageRoot, "templates");
793
1049
  }
794
1050
  function createInstallResult(targetDir) {
795
1051
  return {
@@ -809,12 +1065,27 @@ async function ensureTargetDirectory(targetDir) {
809
1065
  }
810
1066
  async function handleBackup(targetDir, shouldBackup, result) {
811
1067
  if (!shouldBackup)
812
- return;
1068
+ return null;
1069
+ const layout = getProviderLayout();
1070
+ const rootDir = join4(targetDir, layout.rootDir);
1071
+ let preservation = null;
1072
+ if (await fileExists(rootDir)) {
1073
+ const { createTempDir: createTempDir2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
1074
+ const tempDir = await createTempDir2("omcustom-preserve-");
1075
+ preservation = await extractCriticalFiles(rootDir, tempDir);
1076
+ if (preservation.extractedFiles.length > 0 || preservation.extractedDirs.length > 0) {
1077
+ info("install.preserved", {
1078
+ files: String(preservation.extractedFiles.length),
1079
+ dirs: String(preservation.extractedDirs.length)
1080
+ });
1081
+ }
1082
+ }
813
1083
  const backupPaths = await backupExistingInstallation(targetDir);
814
1084
  result.backedUpPaths.push(...backupPaths);
815
1085
  if (backupPaths.length > 0) {
816
1086
  info("install.backup", { path: backupPaths[0] });
817
1087
  }
1088
+ return preservation;
818
1089
  }
819
1090
  async function checkAndWarnExisting(targetDir, force, backup, result) {
820
1091
  if (force || backup)
@@ -853,8 +1124,8 @@ async function installSingleComponent(targetDir, component, options, result) {
853
1124
  }
854
1125
  async function installStatusline(targetDir, options, _result) {
855
1126
  const layout = getProviderLayout();
856
- const srcPath = resolveTemplatePath(join3(layout.rootDir, "statusline.sh"));
857
- const destPath = join3(targetDir, layout.rootDir, "statusline.sh");
1127
+ const srcPath = resolveTemplatePath(join4(layout.rootDir, "statusline.sh"));
1128
+ const destPath = join4(targetDir, layout.rootDir, "statusline.sh");
858
1129
  if (!await fileExists(srcPath)) {
859
1130
  debug("install.statusline_not_found", { path: srcPath });
860
1131
  return;
@@ -872,7 +1143,7 @@ async function installStatusline(targetDir, options, _result) {
872
1143
  }
873
1144
  async function installSettingsLocal(targetDir, result) {
874
1145
  const layout = getProviderLayout();
875
- const settingsPath = join3(targetDir, layout.rootDir, "settings.local.json");
1146
+ const settingsPath = join4(targetDir, layout.rootDir, "settings.local.json");
876
1147
  const statusLineConfig = {
877
1148
  statusLine: {
878
1149
  type: "command",
@@ -920,13 +1191,30 @@ async function install(options) {
920
1191
  try {
921
1192
  info("install.start", { targetDir: options.targetDir });
922
1193
  await ensureTargetDirectory(options.targetDir);
923
- await handleBackup(options.targetDir, !!options.backup, result);
1194
+ const preservation = await handleBackup(options.targetDir, !!options.backup, result);
924
1195
  await checkAndWarnExisting(options.targetDir, !!options.force, !!options.backup, result);
925
1196
  await verifyTemplateDirectory();
926
1197
  await installAllComponents(options.targetDir, options, result);
927
1198
  await installStatusline(options.targetDir, options, result);
928
1199
  await installSettingsLocal(options.targetDir, result);
929
1200
  await installEntryDocWithTracking(options.targetDir, options, result);
1201
+ if (preservation) {
1202
+ const layout = getProviderLayout();
1203
+ const rootDir = join4(options.targetDir, layout.rootDir);
1204
+ const restoration = await restoreCriticalFiles(rootDir, preservation);
1205
+ if (restoration.restoredFiles.length > 0 || restoration.restoredDirs.length > 0) {
1206
+ info("install.restored", {
1207
+ files: String(restoration.restoredFiles.length),
1208
+ dirs: String(restoration.restoredDirs.length)
1209
+ });
1210
+ }
1211
+ if (restoration.failures.length > 0) {
1212
+ for (const failure of restoration.failures) {
1213
+ result.warnings.push(`Failed to restore ${failure.path}: ${failure.reason}`);
1214
+ }
1215
+ }
1216
+ await cleanupPreservation(preservation.tempDir);
1217
+ }
930
1218
  await updateInstallConfig(options.targetDir, options, result.installedComponents);
931
1219
  result.success = true;
932
1220
  success("install.success");
@@ -939,7 +1227,7 @@ async function install(options) {
939
1227
  }
940
1228
  async function copyTemplates(targetDir, templatePath, options) {
941
1229
  const srcPath = resolveTemplatePath(templatePath);
942
- const destPath = join3(targetDir, templatePath);
1230
+ const destPath = join4(targetDir, templatePath);
943
1231
  await copyDirectory(srcPath, destPath, {
944
1232
  overwrite: options?.overwrite ?? false,
945
1233
  preserveSymlinks: options?.preserveSymlinks ?? true,
@@ -949,14 +1237,14 @@ async function copyTemplates(targetDir, templatePath, options) {
949
1237
  async function createDirectoryStructure(targetDir) {
950
1238
  const layout = getProviderLayout();
951
1239
  for (const dir of layout.directoryStructure) {
952
- const fullPath = join3(targetDir, dir);
1240
+ const fullPath = join4(targetDir, dir);
953
1241
  await ensureDirectory(fullPath);
954
1242
  }
955
1243
  }
956
1244
  async function getTemplateManifest() {
957
1245
  const packageRoot = getPackageRoot();
958
1246
  const layout = getProviderLayout();
959
- const manifestPath = join3(packageRoot, "templates", layout.manifestFile);
1247
+ const manifestPath = join4(packageRoot, "templates", layout.manifestFile);
960
1248
  if (await fileExists(manifestPath)) {
961
1249
  return readJsonFile(manifestPath);
962
1250
  }
@@ -980,7 +1268,7 @@ async function installComponent(targetDir, component, options) {
980
1268
  return false;
981
1269
  }
982
1270
  const templatePath = getComponentPath(component);
983
- const destPath = join3(targetDir, templatePath);
1271
+ const destPath = join4(targetDir, templatePath);
984
1272
  const destExists = await fileExists(destPath);
985
1273
  if (destExists && !options.force && !options.backup) {
986
1274
  debug("install.component_skipped", { component });
@@ -1008,7 +1296,7 @@ async function installEntryDoc(targetDir, language, overwrite = false) {
1008
1296
  const layout = getProviderLayout();
1009
1297
  const templateFile = getEntryTemplateName(language);
1010
1298
  const srcPath = resolveTemplatePath(templateFile);
1011
- const destPath = join3(targetDir, layout.entryFile);
1299
+ const destPath = join4(targetDir, layout.entryFile);
1012
1300
  if (!await fileExists(srcPath)) {
1013
1301
  warn("install.entry_md_not_found", { language, path: srcPath, entry: layout.entryFile });
1014
1302
  return false;
@@ -1028,8 +1316,8 @@ async function installEntryDoc(targetDir, language, overwrite = false) {
1028
1316
  return true;
1029
1317
  }
1030
1318
  async function backupExisting(sourcePath, backupDir) {
1031
- const name = basename(sourcePath);
1032
- const backupPath = join3(backupDir, name);
1319
+ const name = basename2(sourcePath);
1320
+ const backupPath = join4(backupDir, name);
1033
1321
  await rename(sourcePath, backupPath);
1034
1322
  return backupPath;
1035
1323
  }
@@ -1038,7 +1326,7 @@ async function checkExistingPaths(targetDir) {
1038
1326
  const pathsToCheck = [layout.entryFile, layout.rootDir, "guides"];
1039
1327
  const existingPaths = [];
1040
1328
  for (const relativePath of pathsToCheck) {
1041
- const fullPath = join3(targetDir, relativePath);
1329
+ const fullPath = join4(targetDir, relativePath);
1042
1330
  if (await fileExists(fullPath)) {
1043
1331
  existingPaths.push(relativePath);
1044
1332
  }
@@ -1052,11 +1340,11 @@ async function backupExistingInstallation(targetDir) {
1052
1340
  return [];
1053
1341
  }
1054
1342
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
1055
- const backupDir = join3(targetDir, `${layout.backupDirPrefix}${timestamp}`);
1343
+ const backupDir = join4(targetDir, `${layout.backupDirPrefix}${timestamp}`);
1056
1344
  await ensureDirectory(backupDir);
1057
1345
  const backedUpPaths = [];
1058
1346
  for (const relativePath of existingPaths) {
1059
- const fullPath = join3(targetDir, relativePath);
1347
+ const fullPath = join4(targetDir, relativePath);
1060
1348
  try {
1061
1349
  const backupPath = await backupExisting(fullPath, backupDir);
1062
1350
  backedUpPaths.push(backupPath);
@@ -1078,7 +1366,8 @@ async function detectProvider(_options = {}) {
1078
1366
  };
1079
1367
  }
1080
1368
  // src/core/updater.ts
1081
- import { join as join4 } from "node:path";
1369
+ init_fs();
1370
+ import { join as join5 } from "node:path";
1082
1371
 
1083
1372
  // src/core/entry-merger.ts
1084
1373
  var MANAGED_START = "<!-- omcustom:start -->";
@@ -1327,7 +1616,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
1327
1616
  }
1328
1617
  async function updateEntryDoc(targetDir, config, options) {
1329
1618
  const layout = getProviderLayout();
1330
- const entryPath = join4(targetDir, layout.entryFile);
1619
+ const entryPath = join5(targetDir, layout.entryFile);
1331
1620
  const templateName = getEntryTemplateName2(config.language);
1332
1621
  const templatePath = resolveTemplatePath(templateName);
1333
1622
  if (!await fileExists(templatePath)) {
@@ -1440,8 +1729,8 @@ async function checkForUpdates(targetDir) {
1440
1729
  async function applyUpdates(targetDir, updates) {
1441
1730
  const fs = await import("node:fs/promises");
1442
1731
  for (const update2 of updates) {
1443
- const fullPath = join4(targetDir, update2.path);
1444
- await ensureDirectory(join4(fullPath, ".."));
1732
+ const fullPath = join5(targetDir, update2.path);
1733
+ await ensureDirectory(join5(fullPath, ".."));
1445
1734
  await fs.writeFile(fullPath, update2.content, "utf-8");
1446
1735
  debug("update.file_applied", { path: update2.path });
1447
1736
  }
@@ -1450,7 +1739,7 @@ async function preserveCustomizations(targetDir, customizations) {
1450
1739
  const preserved = new Map;
1451
1740
  const fs = await import("node:fs/promises");
1452
1741
  for (const filePath of customizations) {
1453
- const fullPath = join4(targetDir, filePath);
1742
+ const fullPath = join5(targetDir, filePath);
1454
1743
  if (await fileExists(fullPath)) {
1455
1744
  const content = await fs.readFile(fullPath, "utf-8");
1456
1745
  preserved.set(filePath, content);
@@ -1482,7 +1771,7 @@ async function updateComponent(targetDir, component, customizations, options, co
1482
1771
  const preservedFiles = [];
1483
1772
  const componentPath = getComponentPath2(component);
1484
1773
  const srcPath = resolveTemplatePath(componentPath);
1485
- const destPath = join4(targetDir, componentPath);
1774
+ const destPath = join5(targetDir, componentPath);
1486
1775
  const customComponents = config.customComponents || [];
1487
1776
  const skipPaths = [];
1488
1777
  if (customizations && !options.forceOverwriteAll) {
@@ -1496,7 +1785,7 @@ async function updateComponent(targetDir, component, customizations, options, co
1496
1785
  }
1497
1786
  }
1498
1787
  const path = await import("node:path");
1499
- const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath, join4(targetDir, p)));
1788
+ const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath, join5(targetDir, p)));
1500
1789
  await copyDirectory(srcPath, destPath, {
1501
1790
  overwrite: true,
1502
1791
  skipPaths: normalizedSkipPaths.length > 0 ? normalizedSkipPaths : undefined
@@ -1516,12 +1805,12 @@ async function syncRootLevelFiles(targetDir, options) {
1516
1805
  const layout = getProviderLayout();
1517
1806
  const synced = [];
1518
1807
  for (const fileName of ROOT_LEVEL_FILES) {
1519
- const srcPath = resolveTemplatePath(join4(layout.rootDir, fileName));
1808
+ const srcPath = resolveTemplatePath(join5(layout.rootDir, fileName));
1520
1809
  if (!await fileExists(srcPath)) {
1521
1810
  continue;
1522
1811
  }
1523
- const destPath = join4(targetDir, layout.rootDir, fileName);
1524
- await ensureDirectory(join4(destPath, ".."));
1812
+ const destPath = join5(targetDir, layout.rootDir, fileName);
1813
+ await ensureDirectory(join5(destPath, ".."));
1525
1814
  await fs.copyFile(srcPath, destPath);
1526
1815
  if (fileName.endsWith(".sh")) {
1527
1816
  await fs.chmod(destPath, 493);
@@ -1556,7 +1845,7 @@ async function removeDeprecatedFiles(targetDir, options) {
1556
1845
  });
1557
1846
  continue;
1558
1847
  }
1559
- const fullPath = join4(targetDir, entry.path);
1848
+ const fullPath = join5(targetDir, entry.path);
1560
1849
  if (await fileExists(fullPath)) {
1561
1850
  await fs.unlink(fullPath);
1562
1851
  removed.push(entry.path);
@@ -1580,26 +1869,26 @@ function getComponentPath2(component) {
1580
1869
  }
1581
1870
  async function backupInstallation(targetDir) {
1582
1871
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
1583
- const backupDir = join4(targetDir, `.omcustom-backup-${timestamp}`);
1872
+ const backupDir = join5(targetDir, `.omcustom-backup-${timestamp}`);
1584
1873
  const fs = await import("node:fs/promises");
1585
1874
  await ensureDirectory(backupDir);
1586
1875
  const layout = getProviderLayout();
1587
1876
  const dirsToBackup = [layout.rootDir, "guides"];
1588
1877
  for (const dir of dirsToBackup) {
1589
- const srcPath = join4(targetDir, dir);
1878
+ const srcPath = join5(targetDir, dir);
1590
1879
  if (await fileExists(srcPath)) {
1591
- const destPath = join4(backupDir, dir);
1880
+ const destPath = join5(backupDir, dir);
1592
1881
  await copyDirectory(srcPath, destPath, { overwrite: true });
1593
1882
  }
1594
1883
  }
1595
- const entryPath = join4(targetDir, layout.entryFile);
1884
+ const entryPath = join5(targetDir, layout.entryFile);
1596
1885
  if (await fileExists(entryPath)) {
1597
- await fs.copyFile(entryPath, join4(backupDir, layout.entryFile));
1886
+ await fs.copyFile(entryPath, join5(backupDir, layout.entryFile));
1598
1887
  }
1599
1888
  return backupDir;
1600
1889
  }
1601
1890
  async function loadCustomizationManifest(targetDir) {
1602
- const manifestPath = join4(targetDir, CUSTOMIZATION_MANIFEST_FILE);
1891
+ const manifestPath = join5(targetDir, CUSTOMIZATION_MANIFEST_FILE);
1603
1892
  if (await fileExists(manifestPath)) {
1604
1893
  return readJsonFile(manifestPath);
1605
1894
  }
@@ -1607,6 +1896,7 @@ async function loadCustomizationManifest(targetDir) {
1607
1896
  }
1608
1897
 
1609
1898
  // src/index.ts
1899
+ init_fs();
1610
1900
  var VERSION = "0.0.0";
1611
1901
  var src_default = {
1612
1902
  VERSION
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-customcode",
3
- "version": "0.24.2",
3
+ "version": "0.30.1",
4
4
  "description": "Batteries-included agent harness for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: be-django-expert
3
+ description: Expert Django developer for production-ready Python web applications. Use for Django projects, models/views/templates, Django REST Framework, authentication, admin customization, and deployment optimization.
4
+ model: sonnet
5
+ memory: project
6
+ effort: high
7
+ skills:
8
+ - django-best-practices
9
+ tools:
10
+ - Read
11
+ - Write
12
+ - Edit
13
+ - Grep
14
+ - Glob
15
+ - Bash
16
+ ---
17
+
18
+ You are an expert Django developer specialized in building production-ready Python web applications following best practices and modern patterns.
19
+
20
+ ## Capabilities
21
+
22
+ - Design scalable Django application architecture with proper app structure
23
+ - Implement models with custom managers, querysets, and constraints
24
+ - Build class-based and function-based views with proper permissions
25
+ - Create REST APIs using Django REST Framework (DRF)
26
+ - Customize Django admin for internal tooling
27
+ - Implement authentication and authorization patterns
28
+ - Optimize database queries (N+1 prevention, indexing, bulk ops)
29
+ - Configure and deploy Django applications securely
30
+
31
+ ## Skills
32
+
33
+ Apply the **django-best-practices** skill for Django development patterns.
34
+
35
+ ## Reference Guides
36
+
37
+ Consult the **django-best-practices** guide at `guides/django-best-practices/` for Django reference documentation.
38
+
39
+ ## Workflow
40
+
41
+ 1. Understand requirements and identify affected Django components
42
+ 2. Apply django-best-practices skill for patterns and conventions
43
+ 3. Reference django-best-practices guide for specifics
44
+ 4. Write/review code with proper model design, view logic, and URL patterns
45
+ 5. Ensure security checklist compliance and proper test coverage
@@ -202,6 +202,16 @@
202
202
  ],
203
203
  "description": "Record agent/task outcomes (success/failure) for model escalation decisions"
204
204
  },
205
+ {
206
+ "matcher": "tool == \"Edit\" || tool == \"Write\" || tool == \"Bash\" || tool == \"Task\" || tool == \"Agent\" || tool == \"Read\" || tool == \"Glob\" || tool == \"Grep\"",
207
+ "hooks": [
208
+ {
209
+ "type": "command",
210
+ "command": "bash .claude/hooks/scripts/context-budget-advisor.sh"
211
+ }
212
+ ],
213
+ "description": "Context budget advisor — track tool usage patterns and advise ecomode activation"
214
+ },
205
215
  {
206
216
  "matcher": "tool == \"Edit\" || tool == \"Write\" || tool == \"Bash\" || tool == \"Task\" || tool == \"Agent\"",
207
217
  "hooks": [