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/README.md +3 -3
- package/dist/cli/index.js +518 -245
- package/dist/index.js +327 -37
- package/package.json +1 -1
- package/templates/.claude/agents/be-django-expert.md +45 -0
- package/templates/.claude/hooks/hooks.json +10 -0
- package/templates/.claude/hooks/scripts/context-budget-advisor.sh +86 -0
- package/templates/.claude/hooks/scripts/session-env-check.sh +58 -0
- package/templates/.claude/rules/SHOULD-ecomode.md +39 -0
- package/templates/.claude/rules/SHOULD-memory-integration.md +99 -9
- package/templates/.claude/skills/dev-lead-routing/SKILL.md +2 -1
- package/templates/.claude/skills/django-best-practices/SKILL.md +440 -0
- package/templates/CLAUDE.md.en +5 -5
- package/templates/CLAUDE.md.ko +5 -5
- package/templates/guides/django-best-practices/README.md +476 -0
- package/templates/manifest.json +4 -4
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
|
|
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(
|
|
857
|
-
const destPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1032
|
-
const backupPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
1444
|
-
await ensureDirectory(
|
|
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 =
|
|
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 =
|
|
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,
|
|
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(
|
|
1808
|
+
const srcPath = resolveTemplatePath(join5(layout.rootDir, fileName));
|
|
1520
1809
|
if (!await fileExists(srcPath)) {
|
|
1521
1810
|
continue;
|
|
1522
1811
|
}
|
|
1523
|
-
const destPath =
|
|
1524
|
-
await ensureDirectory(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1878
|
+
const srcPath = join5(targetDir, dir);
|
|
1590
1879
|
if (await fileExists(srcPath)) {
|
|
1591
|
-
const destPath =
|
|
1880
|
+
const destPath = join5(backupDir, dir);
|
|
1592
1881
|
await copyDirectory(srcPath, destPath, { overwrite: true });
|
|
1593
1882
|
}
|
|
1594
1883
|
}
|
|
1595
|
-
const entryPath =
|
|
1884
|
+
const entryPath = join5(targetDir, layout.entryFile);
|
|
1596
1885
|
if (await fileExists(entryPath)) {
|
|
1597
|
-
await fs.copyFile(entryPath,
|
|
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 =
|
|
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
|
@@ -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": [
|