oh-my-customcode 0.79.1 → 0.79.3
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 +664 -346
- package/dist/index.js +212 -41
- package/package.json +1 -1
- package/templates/.claude/skills/dev-lead-routing/SKILL.md +3 -1
- package/templates/manifest.json +1 -1
package/dist/index.js
CHANGED
|
@@ -291,6 +291,159 @@ function resolvePath(...paths) {
|
|
|
291
291
|
}
|
|
292
292
|
var init_fs = () => {};
|
|
293
293
|
|
|
294
|
+
// src/core/registry.ts
|
|
295
|
+
var exports_registry = {};
|
|
296
|
+
__export(exports_registry, {
|
|
297
|
+
unregisterProject: () => unregisterProject,
|
|
298
|
+
registryToList: () => registryToList,
|
|
299
|
+
registerProject: () => registerProject,
|
|
300
|
+
readRegistry: () => readRegistry,
|
|
301
|
+
migrateFromLockfiles: () => migrateFromLockfiles,
|
|
302
|
+
cleanRegistry: () => cleanRegistry,
|
|
303
|
+
_setRegistryDirForTesting: () => _setRegistryDirForTesting
|
|
304
|
+
});
|
|
305
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
306
|
+
import { homedir } from "node:os";
|
|
307
|
+
import { basename as basename3, join as join6, resolve as resolve2 } from "node:path";
|
|
308
|
+
function _setRegistryDirForTesting(dir2) {
|
|
309
|
+
_registryDirOverride = dir2;
|
|
310
|
+
}
|
|
311
|
+
function registryDir() {
|
|
312
|
+
if (_registryDirOverride !== undefined)
|
|
313
|
+
return _registryDirOverride;
|
|
314
|
+
const home = process.env.HOME ?? homedir();
|
|
315
|
+
return join6(home, ".oh-my-customcode");
|
|
316
|
+
}
|
|
317
|
+
function registryPath() {
|
|
318
|
+
return join6(registryDir(), "projects.json");
|
|
319
|
+
}
|
|
320
|
+
async function readRegistryRaw() {
|
|
321
|
+
try {
|
|
322
|
+
const content = await readFile(registryPath(), "utf-8");
|
|
323
|
+
const parsed = JSON.parse(content);
|
|
324
|
+
if (typeof parsed === "object" && parsed !== null && "projects" in parsed && typeof parsed.projects === "object" && parsed.projects !== null) {
|
|
325
|
+
return parsed;
|
|
326
|
+
}
|
|
327
|
+
return { ...EMPTY_REGISTRY };
|
|
328
|
+
} catch {
|
|
329
|
+
return { ...EMPTY_REGISTRY };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async function writeRegistry(registry) {
|
|
333
|
+
const dir2 = registryDir();
|
|
334
|
+
await mkdir(dir2, { recursive: true });
|
|
335
|
+
await writeFile(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
336
|
+
}
|
|
337
|
+
async function readRegistry() {
|
|
338
|
+
return readRegistryRaw();
|
|
339
|
+
}
|
|
340
|
+
async function registerProject(projectPath, version) {
|
|
341
|
+
const normalizedPath = resolve2(projectPath);
|
|
342
|
+
const registry = await readRegistryRaw();
|
|
343
|
+
const existing = registry.projects[normalizedPath];
|
|
344
|
+
const now = new Date().toISOString();
|
|
345
|
+
registry.projects[normalizedPath] = {
|
|
346
|
+
version,
|
|
347
|
+
installedAt: existing?.installedAt ?? now,
|
|
348
|
+
updatedAt: now
|
|
349
|
+
};
|
|
350
|
+
await writeRegistry(registry);
|
|
351
|
+
}
|
|
352
|
+
async function unregisterProject(projectPath) {
|
|
353
|
+
const normalizedPath = resolve2(projectPath);
|
|
354
|
+
const registry = await readRegistryRaw();
|
|
355
|
+
if (!(normalizedPath in registry.projects))
|
|
356
|
+
return;
|
|
357
|
+
delete registry.projects[normalizedPath];
|
|
358
|
+
await writeRegistry(registry);
|
|
359
|
+
}
|
|
360
|
+
async function cleanRegistry() {
|
|
361
|
+
const { access: fsAccess } = await import("node:fs/promises");
|
|
362
|
+
const registry = await readRegistryRaw();
|
|
363
|
+
const paths = Object.keys(registry.projects);
|
|
364
|
+
let removed = 0;
|
|
365
|
+
for (const projectPath of paths) {
|
|
366
|
+
try {
|
|
367
|
+
await fsAccess(projectPath);
|
|
368
|
+
} catch {
|
|
369
|
+
delete registry.projects[projectPath];
|
|
370
|
+
removed++;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (removed > 0) {
|
|
374
|
+
await writeRegistry(registry);
|
|
375
|
+
}
|
|
376
|
+
return removed;
|
|
377
|
+
}
|
|
378
|
+
async function parseLockFile(lockPath) {
|
|
379
|
+
try {
|
|
380
|
+
const content = await readFile(lockPath, "utf-8");
|
|
381
|
+
const lock = JSON.parse(content);
|
|
382
|
+
const version = typeof lock.version === "string" ? lock.version : typeof lock.templateVersion === "string" ? lock.templateVersion : "0.0.0";
|
|
383
|
+
const now = new Date().toISOString();
|
|
384
|
+
return {
|
|
385
|
+
version,
|
|
386
|
+
installedAt: typeof lock.installedAt === "string" ? lock.installedAt : now,
|
|
387
|
+
updatedAt: typeof lock.updatedAt === "string" ? lock.updatedAt : now
|
|
388
|
+
};
|
|
389
|
+
} catch {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async function migrateFromLockfiles(searchDirs) {
|
|
394
|
+
const { readdir: readdir3 } = await import("node:fs/promises");
|
|
395
|
+
const MAX_DEPTH = 3;
|
|
396
|
+
const discovered = new Map;
|
|
397
|
+
async function scan(dir2, depth) {
|
|
398
|
+
if (depth > MAX_DEPTH || discovered.has(dir2))
|
|
399
|
+
return;
|
|
400
|
+
let entries;
|
|
401
|
+
try {
|
|
402
|
+
entries = await readdir3(dir2, { withFileTypes: true });
|
|
403
|
+
} catch {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const entry = await parseLockFile(join6(dir2, ".omcustom.lock.json"));
|
|
407
|
+
if (entry !== null) {
|
|
408
|
+
discovered.set(resolve2(dir2), entry);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (depth < MAX_DEPTH) {
|
|
412
|
+
const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SCAN_SKIP_DIRS.has(e.name));
|
|
413
|
+
await Promise.all(subdirs.map((sub) => scan(join6(dir2, sub.name), depth + 1)));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
await Promise.all(searchDirs.map((dir2) => scan(dir2, 0).catch(() => {})));
|
|
417
|
+
if (discovered.size === 0)
|
|
418
|
+
return 0;
|
|
419
|
+
const registry = await readRegistryRaw();
|
|
420
|
+
let imported = 0;
|
|
421
|
+
for (const [projectPath, entry] of discovered) {
|
|
422
|
+
if (!(projectPath in registry.projects)) {
|
|
423
|
+
registry.projects[projectPath] = entry;
|
|
424
|
+
imported++;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (imported > 0) {
|
|
428
|
+
await writeRegistry(registry);
|
|
429
|
+
}
|
|
430
|
+
return imported;
|
|
431
|
+
}
|
|
432
|
+
function registryToList(registry) {
|
|
433
|
+
return Object.entries(registry.projects).map(([path, entry]) => ({
|
|
434
|
+
name: basename3(path),
|
|
435
|
+
path,
|
|
436
|
+
version: entry.version,
|
|
437
|
+
installedAt: entry.installedAt,
|
|
438
|
+
updatedAt: entry.updatedAt
|
|
439
|
+
}));
|
|
440
|
+
}
|
|
441
|
+
var _registryDirOverride, EMPTY_REGISTRY, SCAN_SKIP_DIRS;
|
|
442
|
+
var init_registry = __esm(() => {
|
|
443
|
+
EMPTY_REGISTRY = { projects: {} };
|
|
444
|
+
SCAN_SKIP_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
|
|
445
|
+
});
|
|
446
|
+
|
|
294
447
|
// src/core/config.ts
|
|
295
448
|
init_fs();
|
|
296
449
|
import { join as join2 } from "node:path";
|
|
@@ -1813,14 +1966,14 @@ async function detectProvider(_options = {}) {
|
|
|
1813
1966
|
};
|
|
1814
1967
|
}
|
|
1815
1968
|
// src/core/updater.ts
|
|
1816
|
-
import { join as
|
|
1969
|
+
import { join as join7 } from "node:path";
|
|
1817
1970
|
// package.json
|
|
1818
1971
|
var package_default = {
|
|
1819
1972
|
name: "oh-my-customcode",
|
|
1820
1973
|
workspaces: [
|
|
1821
1974
|
"packages/*"
|
|
1822
1975
|
],
|
|
1823
|
-
version: "0.79.
|
|
1976
|
+
version: "0.79.3",
|
|
1824
1977
|
description: "Batteries-included agent harness for Claude Code",
|
|
1825
1978
|
type: "module",
|
|
1826
1979
|
bin: {
|
|
@@ -4586,7 +4739,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
|
|
|
4586
4739
|
}
|
|
4587
4740
|
async function updateEntryDoc(targetDir, config, options) {
|
|
4588
4741
|
const layout = getProviderLayout();
|
|
4589
|
-
const entryPath =
|
|
4742
|
+
const entryPath = join7(targetDir, layout.entryFile);
|
|
4590
4743
|
const templateName = getEntryTemplateName2(config.language);
|
|
4591
4744
|
const templatePath = resolveTemplatePath(templateName);
|
|
4592
4745
|
if (!await fileExists(templatePath)) {
|
|
@@ -4665,6 +4818,36 @@ function checkAndInstallRtkAfterUpdate() {
|
|
|
4665
4818
|
}
|
|
4666
4819
|
}
|
|
4667
4820
|
}
|
|
4821
|
+
async function updateProjectRegistry(targetDir, newVersion) {
|
|
4822
|
+
try {
|
|
4823
|
+
const { registerProject: registerProject2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
4824
|
+
await registerProject2(targetDir, newVersion);
|
|
4825
|
+
} catch {}
|
|
4826
|
+
}
|
|
4827
|
+
async function regenerateLockfile(targetDir, result) {
|
|
4828
|
+
const lockfileResult = await generateAndWriteLockfileForDir(targetDir);
|
|
4829
|
+
if (lockfileResult.warning) {
|
|
4830
|
+
result.warnings.push(lockfileResult.warning);
|
|
4831
|
+
warn("update.lockfile_failed", { error: lockfileResult.warning });
|
|
4832
|
+
} else {
|
|
4833
|
+
debug("update.lockfile_regenerated", {
|
|
4834
|
+
files: String(lockfileResult.fileCount)
|
|
4835
|
+
});
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
async function shouldSkipSelfUpdate(targetDir, result) {
|
|
4839
|
+
const targetPkgPath = join7(targetDir, "package.json");
|
|
4840
|
+
if (await fileExists(targetPkgPath)) {
|
|
4841
|
+
const targetPkg = await readJsonFile(targetPkgPath);
|
|
4842
|
+
if (targetPkg.name === "oh-my-customcode") {
|
|
4843
|
+
warn("update.self_update_skipped");
|
|
4844
|
+
result.success = true;
|
|
4845
|
+
result.warnings.push("Skipped: source project cannot update itself");
|
|
4846
|
+
return true;
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
return false;
|
|
4850
|
+
}
|
|
4668
4851
|
function checkAndInstallCodexAfterUpdate() {
|
|
4669
4852
|
if (!isCodexInstalled()) {
|
|
4670
4853
|
warn("update.codex_missing");
|
|
@@ -4687,15 +4870,8 @@ async function update(options) {
|
|
|
4687
4870
|
result.error = `Downgrade prevented: project has v${result.previousVersion} but CLI is v${cliVersion}. Update the CLI first: npm install -g oh-my-customcode@latest`;
|
|
4688
4871
|
return result;
|
|
4689
4872
|
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
const targetPkg = await readJsonFile(targetPkgPath);
|
|
4693
|
-
if (targetPkg.name === "oh-my-customcode") {
|
|
4694
|
-
warn("update.self_update_skipped");
|
|
4695
|
-
result.success = true;
|
|
4696
|
-
result.warnings.push("Skipped: source project cannot update itself");
|
|
4697
|
-
return result;
|
|
4698
|
-
}
|
|
4873
|
+
if (await shouldSkipSelfUpdate(options.targetDir, result)) {
|
|
4874
|
+
return result;
|
|
4699
4875
|
}
|
|
4700
4876
|
const updateCheck = await checkForUpdates(options.targetDir);
|
|
4701
4877
|
result.newVersion = updateCheck.latestVersion;
|
|
@@ -4713,17 +4889,12 @@ async function update(options) {
|
|
|
4713
4889
|
const components = options.components || getAllUpdateComponents();
|
|
4714
4890
|
await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
|
|
4715
4891
|
await runFullUpdatePostProcessing(options, result, config);
|
|
4716
|
-
|
|
4717
|
-
if (lockfileResult.warning) {
|
|
4718
|
-
result.warnings.push(lockfileResult.warning);
|
|
4719
|
-
warn("update.lockfile_failed", { error: lockfileResult.warning });
|
|
4720
|
-
} else {
|
|
4721
|
-
debug("update.lockfile_regenerated", {
|
|
4722
|
-
files: String(lockfileResult.fileCount)
|
|
4723
|
-
});
|
|
4724
|
-
}
|
|
4892
|
+
await regenerateLockfile(options.targetDir, result);
|
|
4725
4893
|
checkAndInstallRtkAfterUpdate();
|
|
4726
4894
|
checkAndInstallCodexAfterUpdate();
|
|
4895
|
+
if (result.success && !options.dryRun) {
|
|
4896
|
+
await updateProjectRegistry(options.targetDir, result.newVersion);
|
|
4897
|
+
}
|
|
4727
4898
|
} catch (err) {
|
|
4728
4899
|
const message = err instanceof Error ? err.message : String(err);
|
|
4729
4900
|
result.error = message;
|
|
@@ -4757,8 +4928,8 @@ async function checkForUpdates(targetDir) {
|
|
|
4757
4928
|
async function applyUpdates(targetDir, updates) {
|
|
4758
4929
|
const fs = await import("node:fs/promises");
|
|
4759
4930
|
for (const update2 of updates) {
|
|
4760
|
-
const fullPath =
|
|
4761
|
-
await ensureDirectory(
|
|
4931
|
+
const fullPath = join7(targetDir, update2.path);
|
|
4932
|
+
await ensureDirectory(join7(fullPath, ".."));
|
|
4762
4933
|
await fs.writeFile(fullPath, update2.content, "utf-8");
|
|
4763
4934
|
debug("update.file_applied", { path: update2.path });
|
|
4764
4935
|
}
|
|
@@ -4767,7 +4938,7 @@ async function preserveCustomizations(targetDir, customizations) {
|
|
|
4767
4938
|
const preserved = new Map;
|
|
4768
4939
|
const fs = await import("node:fs/promises");
|
|
4769
4940
|
for (const filePath of customizations) {
|
|
4770
|
-
const fullPath =
|
|
4941
|
+
const fullPath = join7(targetDir, filePath);
|
|
4771
4942
|
if (await fileExists(fullPath)) {
|
|
4772
4943
|
const content = await fs.readFile(fullPath, "utf-8");
|
|
4773
4944
|
preserved.set(filePath, content);
|
|
@@ -4824,11 +4995,11 @@ async function collectProtectedSkipPaths(srcPath, destPath, componentPath, force
|
|
|
4824
4995
|
const warnedPaths = [];
|
|
4825
4996
|
const updatedPaths = [];
|
|
4826
4997
|
for (const p of protectedRelative) {
|
|
4827
|
-
const targetFilePath =
|
|
4998
|
+
const targetFilePath = join7(targetDir, componentPath, p);
|
|
4828
4999
|
const lockfileKey = `${componentPath}/${p}`.replace(/\\/g, "/");
|
|
4829
5000
|
const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
|
|
4830
5001
|
if (shouldSkip) {
|
|
4831
|
-
skipPaths.push(path.relative(destPath,
|
|
5002
|
+
skipPaths.push(path.relative(destPath, join7(destPath, p)));
|
|
4832
5003
|
warnedPaths.push(p);
|
|
4833
5004
|
} else {
|
|
4834
5005
|
updatedPaths.push(p);
|
|
@@ -4874,7 +5045,7 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
4874
5045
|
const preservedFiles = [];
|
|
4875
5046
|
const componentPath = getComponentPath2(component);
|
|
4876
5047
|
const srcPath = resolveTemplatePath(componentPath);
|
|
4877
|
-
const destPath =
|
|
5048
|
+
const destPath = join7(targetDir, componentPath);
|
|
4878
5049
|
const customComponents = config.customComponents || [];
|
|
4879
5050
|
const skipPaths = [];
|
|
4880
5051
|
if (customizations && !options.forceOverwriteAll) {
|
|
@@ -4916,7 +5087,7 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
4916
5087
|
}
|
|
4917
5088
|
skipPaths.push(...protectedSkipPaths);
|
|
4918
5089
|
const path = await import("node:path");
|
|
4919
|
-
const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath,
|
|
5090
|
+
const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath, join7(targetDir, p)));
|
|
4920
5091
|
const uniqueSkipPaths = [...new Set(normalizedSkipPaths)];
|
|
4921
5092
|
await copyDirectory(srcPath, destPath, {
|
|
4922
5093
|
overwrite: true,
|
|
@@ -4938,12 +5109,12 @@ async function syncRootLevelFiles(targetDir, options) {
|
|
|
4938
5109
|
const layout = getProviderLayout();
|
|
4939
5110
|
const synced = [];
|
|
4940
5111
|
for (const fileName of ROOT_LEVEL_FILES) {
|
|
4941
|
-
const srcPath = resolveTemplatePath(
|
|
5112
|
+
const srcPath = resolveTemplatePath(join7(layout.rootDir, fileName));
|
|
4942
5113
|
if (!await fileExists(srcPath)) {
|
|
4943
5114
|
continue;
|
|
4944
5115
|
}
|
|
4945
|
-
const destPath =
|
|
4946
|
-
await ensureDirectory(
|
|
5116
|
+
const destPath = join7(targetDir, layout.rootDir, fileName);
|
|
5117
|
+
await ensureDirectory(join7(destPath, ".."));
|
|
4947
5118
|
await fs.copyFile(srcPath, destPath);
|
|
4948
5119
|
if (fileName.endsWith(".sh")) {
|
|
4949
5120
|
await fs.chmod(destPath, 493);
|
|
@@ -4978,7 +5149,7 @@ async function removeDeprecatedFiles(targetDir, options) {
|
|
|
4978
5149
|
});
|
|
4979
5150
|
continue;
|
|
4980
5151
|
}
|
|
4981
|
-
const fullPath =
|
|
5152
|
+
const fullPath = join7(targetDir, entry.path);
|
|
4982
5153
|
if (await fileExists(fullPath)) {
|
|
4983
5154
|
await fs.unlink(fullPath);
|
|
4984
5155
|
removed.push(entry.path);
|
|
@@ -5019,7 +5190,7 @@ async function syncNamespaceInFile(targetFilePath, upstreamFilePath) {
|
|
|
5019
5190
|
async function processNamespaceSyncEntry(entry, relPath, fullSrcPath, destPath, componentPath, lockfile) {
|
|
5020
5191
|
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
5021
5192
|
return null;
|
|
5022
|
-
const targetFilePath =
|
|
5193
|
+
const targetFilePath = join7(destPath, relPath);
|
|
5023
5194
|
const lockfileKey = `${componentPath}/${relPath}`.replace(/\\/g, "/");
|
|
5024
5195
|
const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
|
|
5025
5196
|
if (shouldSkip)
|
|
@@ -5034,7 +5205,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
|
|
|
5034
5205
|
return [];
|
|
5035
5206
|
const componentPath = getComponentPath2(component);
|
|
5036
5207
|
const srcPath = resolveTemplatePath(componentPath);
|
|
5037
|
-
const destPath =
|
|
5208
|
+
const destPath = join7(targetDir, componentPath);
|
|
5038
5209
|
const fs = await import("node:fs/promises");
|
|
5039
5210
|
const synced = [];
|
|
5040
5211
|
const queue = [{ dir: srcPath, relDir: "" }];
|
|
@@ -5048,7 +5219,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
|
|
|
5048
5219
|
}
|
|
5049
5220
|
for (const entry of entries) {
|
|
5050
5221
|
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
5051
|
-
const fullSrcPath =
|
|
5222
|
+
const fullSrcPath = join7(dir2, entry.name);
|
|
5052
5223
|
if (entry.isDirectory()) {
|
|
5053
5224
|
queue.push({ dir: fullSrcPath, relDir: relPath });
|
|
5054
5225
|
continue;
|
|
@@ -5071,26 +5242,26 @@ function getComponentPath2(component) {
|
|
|
5071
5242
|
}
|
|
5072
5243
|
async function backupInstallation(targetDir) {
|
|
5073
5244
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
5074
|
-
const backupDir =
|
|
5245
|
+
const backupDir = join7(targetDir, `.omcustom-backup-${timestamp}`);
|
|
5075
5246
|
const fs = await import("node:fs/promises");
|
|
5076
5247
|
await ensureDirectory(backupDir);
|
|
5077
5248
|
const layout = getProviderLayout();
|
|
5078
5249
|
const dirsToBackup = [layout.rootDir, "guides"];
|
|
5079
5250
|
for (const dir2 of dirsToBackup) {
|
|
5080
|
-
const srcPath =
|
|
5251
|
+
const srcPath = join7(targetDir, dir2);
|
|
5081
5252
|
if (await fileExists(srcPath)) {
|
|
5082
|
-
const destPath =
|
|
5253
|
+
const destPath = join7(backupDir, dir2);
|
|
5083
5254
|
await copyDirectory(srcPath, destPath, { overwrite: true });
|
|
5084
5255
|
}
|
|
5085
5256
|
}
|
|
5086
|
-
const entryPath =
|
|
5257
|
+
const entryPath = join7(targetDir, layout.entryFile);
|
|
5087
5258
|
if (await fileExists(entryPath)) {
|
|
5088
|
-
await fs.copyFile(entryPath,
|
|
5259
|
+
await fs.copyFile(entryPath, join7(backupDir, layout.entryFile));
|
|
5089
5260
|
}
|
|
5090
5261
|
return backupDir;
|
|
5091
5262
|
}
|
|
5092
5263
|
async function loadCustomizationManifest(targetDir) {
|
|
5093
|
-
const manifestPath =
|
|
5264
|
+
const manifestPath = join7(targetDir, CUSTOMIZATION_MANIFEST_FILE);
|
|
5094
5265
|
if (await fileExists(manifestPath)) {
|
|
5095
5266
|
return readJsonFile(manifestPath);
|
|
5096
5267
|
}
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ context: fork
|
|
|
16
16
|
| Frontend | fe-vercel-agent, fe-vuejs-agent, fe-svelte-agent, fe-flutter-agent, fe-design-expert |
|
|
17
17
|
| Backend | be-fastapi-expert, be-springboot-expert, be-go-backend-expert, be-nestjs-expert, be-express-expert, be-django-expert |
|
|
18
18
|
| Tooling | tool-npm-expert, tool-optimizer, tool-bun-expert |
|
|
19
|
-
| Database | db-supabase-expert, db-postgres-expert, db-redis-expert |
|
|
19
|
+
| Database | db-supabase-expert, db-postgres-expert, db-redis-expert, db-alembic-expert |
|
|
20
20
|
| Architect | arch-documenter, arch-speckit-agent |
|
|
21
21
|
| Security | sec-codeql-expert |
|
|
22
22
|
| Infra | infra-docker-expert, infra-aws-expert |
|
|
@@ -37,6 +37,7 @@ context: fork
|
|
|
37
37
|
| `.dart`, `pubspec.yaml` | fe-flutter-agent |
|
|
38
38
|
| `.sql` (PG) | db-postgres-expert |
|
|
39
39
|
| `.sql` (Supabase) | db-supabase-expert |
|
|
40
|
+
| `alembic.ini`, `alembic/versions/*.py` | db-alembic-expert |
|
|
40
41
|
| `Dockerfile`, `*.dockerfile` | infra-docker-expert |
|
|
41
42
|
| `*.tf`, `*.tfvars` | infra-aws-expert |
|
|
42
43
|
| `*.yaml`, `*.yml` (CloudFormation) | infra-aws-expert |
|
|
@@ -67,6 +68,7 @@ context: fork
|
|
|
67
68
|
| postgres, postgresql, psql, pg_stat | db-postgres-expert |
|
|
68
69
|
| redis, cache, pub/sub, sorted set | db-redis-expert |
|
|
69
70
|
| supabase, rls, edge function | db-supabase-expert |
|
|
71
|
+
| alembic, migration, db revision, db upgrade, db downgrade | db-alembic-expert |
|
|
70
72
|
| docker, dockerfile, container, compose | infra-docker-expert |
|
|
71
73
|
| aws, cloudformation, vpc, iam, s3, lambda, cdk, terraform | infra-aws-expert |
|
|
72
74
|
| security, codeql, cve, vulnerability, sarif, sast, security audit | sec-codeql-expert |
|
package/templates/manifest.json
CHANGED