oh-my-customcode 0.79.0 → 0.79.2
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 +635 -346
- package/dist/index.js +193 -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,140 @@ 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
|
+
_setRegistryDirForTesting: () => _setRegistryDirForTesting
|
|
303
|
+
});
|
|
304
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
305
|
+
import { homedir } from "node:os";
|
|
306
|
+
import { basename as basename3, join as join6, resolve as resolve2 } from "node:path";
|
|
307
|
+
function _setRegistryDirForTesting(dir2) {
|
|
308
|
+
_registryDirOverride = dir2;
|
|
309
|
+
}
|
|
310
|
+
function registryDir() {
|
|
311
|
+
if (_registryDirOverride !== undefined)
|
|
312
|
+
return _registryDirOverride;
|
|
313
|
+
const home = process.env.HOME ?? homedir();
|
|
314
|
+
return join6(home, ".oh-my-customcode");
|
|
315
|
+
}
|
|
316
|
+
function registryPath() {
|
|
317
|
+
return join6(registryDir(), "projects.json");
|
|
318
|
+
}
|
|
319
|
+
async function readRegistryRaw() {
|
|
320
|
+
try {
|
|
321
|
+
const content = await readFile(registryPath(), "utf-8");
|
|
322
|
+
const parsed = JSON.parse(content);
|
|
323
|
+
if (typeof parsed === "object" && parsed !== null && "projects" in parsed && typeof parsed.projects === "object" && parsed.projects !== null) {
|
|
324
|
+
return parsed;
|
|
325
|
+
}
|
|
326
|
+
return { ...EMPTY_REGISTRY };
|
|
327
|
+
} catch {
|
|
328
|
+
return { ...EMPTY_REGISTRY };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async function writeRegistry(registry) {
|
|
332
|
+
const dir2 = registryDir();
|
|
333
|
+
await mkdir(dir2, { recursive: true });
|
|
334
|
+
await writeFile(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
335
|
+
}
|
|
336
|
+
async function readRegistry() {
|
|
337
|
+
return readRegistryRaw();
|
|
338
|
+
}
|
|
339
|
+
async function registerProject(projectPath, version) {
|
|
340
|
+
const normalizedPath = resolve2(projectPath);
|
|
341
|
+
const registry = await readRegistryRaw();
|
|
342
|
+
const existing = registry.projects[normalizedPath];
|
|
343
|
+
const now = new Date().toISOString();
|
|
344
|
+
registry.projects[normalizedPath] = {
|
|
345
|
+
version,
|
|
346
|
+
installedAt: existing?.installedAt ?? now,
|
|
347
|
+
updatedAt: now
|
|
348
|
+
};
|
|
349
|
+
await writeRegistry(registry);
|
|
350
|
+
}
|
|
351
|
+
async function unregisterProject(projectPath) {
|
|
352
|
+
const normalizedPath = resolve2(projectPath);
|
|
353
|
+
const registry = await readRegistryRaw();
|
|
354
|
+
if (!(normalizedPath in registry.projects))
|
|
355
|
+
return;
|
|
356
|
+
delete registry.projects[normalizedPath];
|
|
357
|
+
await writeRegistry(registry);
|
|
358
|
+
}
|
|
359
|
+
async function parseLockFile(lockPath) {
|
|
360
|
+
try {
|
|
361
|
+
const content = await readFile(lockPath, "utf-8");
|
|
362
|
+
const lock = JSON.parse(content);
|
|
363
|
+
const version = typeof lock.version === "string" ? lock.version : typeof lock.templateVersion === "string" ? lock.templateVersion : "0.0.0";
|
|
364
|
+
const now = new Date().toISOString();
|
|
365
|
+
return {
|
|
366
|
+
version,
|
|
367
|
+
installedAt: typeof lock.installedAt === "string" ? lock.installedAt : now,
|
|
368
|
+
updatedAt: typeof lock.updatedAt === "string" ? lock.updatedAt : now
|
|
369
|
+
};
|
|
370
|
+
} catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async function migrateFromLockfiles(searchDirs) {
|
|
375
|
+
const { readdir: readdir3 } = await import("node:fs/promises");
|
|
376
|
+
const MAX_DEPTH = 3;
|
|
377
|
+
const discovered = new Map;
|
|
378
|
+
async function scan(dir2, depth) {
|
|
379
|
+
if (depth > MAX_DEPTH || discovered.has(dir2))
|
|
380
|
+
return;
|
|
381
|
+
let entries;
|
|
382
|
+
try {
|
|
383
|
+
entries = await readdir3(dir2, { withFileTypes: true });
|
|
384
|
+
} catch {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const entry = await parseLockFile(join6(dir2, ".omcustom.lock.json"));
|
|
388
|
+
if (entry !== null) {
|
|
389
|
+
discovered.set(resolve2(dir2), entry);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (depth < MAX_DEPTH) {
|
|
393
|
+
const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SCAN_SKIP_DIRS.has(e.name));
|
|
394
|
+
await Promise.all(subdirs.map((sub) => scan(join6(dir2, sub.name), depth + 1)));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
await Promise.all(searchDirs.map((dir2) => scan(dir2, 0).catch(() => {})));
|
|
398
|
+
if (discovered.size === 0)
|
|
399
|
+
return 0;
|
|
400
|
+
const registry = await readRegistryRaw();
|
|
401
|
+
let imported = 0;
|
|
402
|
+
for (const [projectPath, entry] of discovered) {
|
|
403
|
+
if (!(projectPath in registry.projects)) {
|
|
404
|
+
registry.projects[projectPath] = entry;
|
|
405
|
+
imported++;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (imported > 0) {
|
|
409
|
+
await writeRegistry(registry);
|
|
410
|
+
}
|
|
411
|
+
return imported;
|
|
412
|
+
}
|
|
413
|
+
function registryToList(registry) {
|
|
414
|
+
return Object.entries(registry.projects).map(([path, entry]) => ({
|
|
415
|
+
name: basename3(path),
|
|
416
|
+
path,
|
|
417
|
+
version: entry.version,
|
|
418
|
+
installedAt: entry.installedAt,
|
|
419
|
+
updatedAt: entry.updatedAt
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
var _registryDirOverride, EMPTY_REGISTRY, SCAN_SKIP_DIRS;
|
|
423
|
+
var init_registry = __esm(() => {
|
|
424
|
+
EMPTY_REGISTRY = { projects: {} };
|
|
425
|
+
SCAN_SKIP_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
|
|
426
|
+
});
|
|
427
|
+
|
|
294
428
|
// src/core/config.ts
|
|
295
429
|
init_fs();
|
|
296
430
|
import { join as join2 } from "node:path";
|
|
@@ -1813,14 +1947,14 @@ async function detectProvider(_options = {}) {
|
|
|
1813
1947
|
};
|
|
1814
1948
|
}
|
|
1815
1949
|
// src/core/updater.ts
|
|
1816
|
-
import { join as
|
|
1950
|
+
import { join as join7 } from "node:path";
|
|
1817
1951
|
// package.json
|
|
1818
1952
|
var package_default = {
|
|
1819
1953
|
name: "oh-my-customcode",
|
|
1820
1954
|
workspaces: [
|
|
1821
1955
|
"packages/*"
|
|
1822
1956
|
],
|
|
1823
|
-
version: "0.79.
|
|
1957
|
+
version: "0.79.2",
|
|
1824
1958
|
description: "Batteries-included agent harness for Claude Code",
|
|
1825
1959
|
type: "module",
|
|
1826
1960
|
bin: {
|
|
@@ -4586,7 +4720,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
|
|
|
4586
4720
|
}
|
|
4587
4721
|
async function updateEntryDoc(targetDir, config, options) {
|
|
4588
4722
|
const layout = getProviderLayout();
|
|
4589
|
-
const entryPath =
|
|
4723
|
+
const entryPath = join7(targetDir, layout.entryFile);
|
|
4590
4724
|
const templateName = getEntryTemplateName2(config.language);
|
|
4591
4725
|
const templatePath = resolveTemplatePath(templateName);
|
|
4592
4726
|
if (!await fileExists(templatePath)) {
|
|
@@ -4665,6 +4799,36 @@ function checkAndInstallRtkAfterUpdate() {
|
|
|
4665
4799
|
}
|
|
4666
4800
|
}
|
|
4667
4801
|
}
|
|
4802
|
+
async function updateProjectRegistry(targetDir, newVersion) {
|
|
4803
|
+
try {
|
|
4804
|
+
const { registerProject: registerProject2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
4805
|
+
await registerProject2(targetDir, newVersion);
|
|
4806
|
+
} catch {}
|
|
4807
|
+
}
|
|
4808
|
+
async function regenerateLockfile(targetDir, result) {
|
|
4809
|
+
const lockfileResult = await generateAndWriteLockfileForDir(targetDir);
|
|
4810
|
+
if (lockfileResult.warning) {
|
|
4811
|
+
result.warnings.push(lockfileResult.warning);
|
|
4812
|
+
warn("update.lockfile_failed", { error: lockfileResult.warning });
|
|
4813
|
+
} else {
|
|
4814
|
+
debug("update.lockfile_regenerated", {
|
|
4815
|
+
files: String(lockfileResult.fileCount)
|
|
4816
|
+
});
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
async function shouldSkipSelfUpdate(targetDir, result) {
|
|
4820
|
+
const targetPkgPath = join7(targetDir, "package.json");
|
|
4821
|
+
if (await fileExists(targetPkgPath)) {
|
|
4822
|
+
const targetPkg = await readJsonFile(targetPkgPath);
|
|
4823
|
+
if (targetPkg.name === "oh-my-customcode") {
|
|
4824
|
+
warn("update.self_update_skipped");
|
|
4825
|
+
result.success = true;
|
|
4826
|
+
result.warnings.push("Skipped: source project cannot update itself");
|
|
4827
|
+
return true;
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
return false;
|
|
4831
|
+
}
|
|
4668
4832
|
function checkAndInstallCodexAfterUpdate() {
|
|
4669
4833
|
if (!isCodexInstalled()) {
|
|
4670
4834
|
warn("update.codex_missing");
|
|
@@ -4687,15 +4851,8 @@ async function update(options) {
|
|
|
4687
4851
|
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
4852
|
return result;
|
|
4689
4853
|
}
|
|
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
|
-
}
|
|
4854
|
+
if (await shouldSkipSelfUpdate(options.targetDir, result)) {
|
|
4855
|
+
return result;
|
|
4699
4856
|
}
|
|
4700
4857
|
const updateCheck = await checkForUpdates(options.targetDir);
|
|
4701
4858
|
result.newVersion = updateCheck.latestVersion;
|
|
@@ -4713,17 +4870,12 @@ async function update(options) {
|
|
|
4713
4870
|
const components = options.components || getAllUpdateComponents();
|
|
4714
4871
|
await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
|
|
4715
4872
|
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
|
-
}
|
|
4873
|
+
await regenerateLockfile(options.targetDir, result);
|
|
4725
4874
|
checkAndInstallRtkAfterUpdate();
|
|
4726
4875
|
checkAndInstallCodexAfterUpdate();
|
|
4876
|
+
if (result.success && !options.dryRun) {
|
|
4877
|
+
await updateProjectRegistry(options.targetDir, result.newVersion);
|
|
4878
|
+
}
|
|
4727
4879
|
} catch (err) {
|
|
4728
4880
|
const message = err instanceof Error ? err.message : String(err);
|
|
4729
4881
|
result.error = message;
|
|
@@ -4757,8 +4909,8 @@ async function checkForUpdates(targetDir) {
|
|
|
4757
4909
|
async function applyUpdates(targetDir, updates) {
|
|
4758
4910
|
const fs = await import("node:fs/promises");
|
|
4759
4911
|
for (const update2 of updates) {
|
|
4760
|
-
const fullPath =
|
|
4761
|
-
await ensureDirectory(
|
|
4912
|
+
const fullPath = join7(targetDir, update2.path);
|
|
4913
|
+
await ensureDirectory(join7(fullPath, ".."));
|
|
4762
4914
|
await fs.writeFile(fullPath, update2.content, "utf-8");
|
|
4763
4915
|
debug("update.file_applied", { path: update2.path });
|
|
4764
4916
|
}
|
|
@@ -4767,7 +4919,7 @@ async function preserveCustomizations(targetDir, customizations) {
|
|
|
4767
4919
|
const preserved = new Map;
|
|
4768
4920
|
const fs = await import("node:fs/promises");
|
|
4769
4921
|
for (const filePath of customizations) {
|
|
4770
|
-
const fullPath =
|
|
4922
|
+
const fullPath = join7(targetDir, filePath);
|
|
4771
4923
|
if (await fileExists(fullPath)) {
|
|
4772
4924
|
const content = await fs.readFile(fullPath, "utf-8");
|
|
4773
4925
|
preserved.set(filePath, content);
|
|
@@ -4824,11 +4976,11 @@ async function collectProtectedSkipPaths(srcPath, destPath, componentPath, force
|
|
|
4824
4976
|
const warnedPaths = [];
|
|
4825
4977
|
const updatedPaths = [];
|
|
4826
4978
|
for (const p of protectedRelative) {
|
|
4827
|
-
const targetFilePath =
|
|
4979
|
+
const targetFilePath = join7(targetDir, componentPath, p);
|
|
4828
4980
|
const lockfileKey = `${componentPath}/${p}`.replace(/\\/g, "/");
|
|
4829
4981
|
const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
|
|
4830
4982
|
if (shouldSkip) {
|
|
4831
|
-
skipPaths.push(path.relative(destPath,
|
|
4983
|
+
skipPaths.push(path.relative(destPath, join7(destPath, p)));
|
|
4832
4984
|
warnedPaths.push(p);
|
|
4833
4985
|
} else {
|
|
4834
4986
|
updatedPaths.push(p);
|
|
@@ -4874,7 +5026,7 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
4874
5026
|
const preservedFiles = [];
|
|
4875
5027
|
const componentPath = getComponentPath2(component);
|
|
4876
5028
|
const srcPath = resolveTemplatePath(componentPath);
|
|
4877
|
-
const destPath =
|
|
5029
|
+
const destPath = join7(targetDir, componentPath);
|
|
4878
5030
|
const customComponents = config.customComponents || [];
|
|
4879
5031
|
const skipPaths = [];
|
|
4880
5032
|
if (customizations && !options.forceOverwriteAll) {
|
|
@@ -4916,7 +5068,7 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
4916
5068
|
}
|
|
4917
5069
|
skipPaths.push(...protectedSkipPaths);
|
|
4918
5070
|
const path = await import("node:path");
|
|
4919
|
-
const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath,
|
|
5071
|
+
const normalizedSkipPaths = skipPaths.map((p) => path.relative(destPath, join7(targetDir, p)));
|
|
4920
5072
|
const uniqueSkipPaths = [...new Set(normalizedSkipPaths)];
|
|
4921
5073
|
await copyDirectory(srcPath, destPath, {
|
|
4922
5074
|
overwrite: true,
|
|
@@ -4938,12 +5090,12 @@ async function syncRootLevelFiles(targetDir, options) {
|
|
|
4938
5090
|
const layout = getProviderLayout();
|
|
4939
5091
|
const synced = [];
|
|
4940
5092
|
for (const fileName of ROOT_LEVEL_FILES) {
|
|
4941
|
-
const srcPath = resolveTemplatePath(
|
|
5093
|
+
const srcPath = resolveTemplatePath(join7(layout.rootDir, fileName));
|
|
4942
5094
|
if (!await fileExists(srcPath)) {
|
|
4943
5095
|
continue;
|
|
4944
5096
|
}
|
|
4945
|
-
const destPath =
|
|
4946
|
-
await ensureDirectory(
|
|
5097
|
+
const destPath = join7(targetDir, layout.rootDir, fileName);
|
|
5098
|
+
await ensureDirectory(join7(destPath, ".."));
|
|
4947
5099
|
await fs.copyFile(srcPath, destPath);
|
|
4948
5100
|
if (fileName.endsWith(".sh")) {
|
|
4949
5101
|
await fs.chmod(destPath, 493);
|
|
@@ -4978,7 +5130,7 @@ async function removeDeprecatedFiles(targetDir, options) {
|
|
|
4978
5130
|
});
|
|
4979
5131
|
continue;
|
|
4980
5132
|
}
|
|
4981
|
-
const fullPath =
|
|
5133
|
+
const fullPath = join7(targetDir, entry.path);
|
|
4982
5134
|
if (await fileExists(fullPath)) {
|
|
4983
5135
|
await fs.unlink(fullPath);
|
|
4984
5136
|
removed.push(entry.path);
|
|
@@ -5019,7 +5171,7 @@ async function syncNamespaceInFile(targetFilePath, upstreamFilePath) {
|
|
|
5019
5171
|
async function processNamespaceSyncEntry(entry, relPath, fullSrcPath, destPath, componentPath, lockfile) {
|
|
5020
5172
|
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
5021
5173
|
return null;
|
|
5022
|
-
const targetFilePath =
|
|
5174
|
+
const targetFilePath = join7(destPath, relPath);
|
|
5023
5175
|
const lockfileKey = `${componentPath}/${relPath}`.replace(/\\/g, "/");
|
|
5024
5176
|
const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
|
|
5025
5177
|
if (shouldSkip)
|
|
@@ -5034,7 +5186,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
|
|
|
5034
5186
|
return [];
|
|
5035
5187
|
const componentPath = getComponentPath2(component);
|
|
5036
5188
|
const srcPath = resolveTemplatePath(componentPath);
|
|
5037
|
-
const destPath =
|
|
5189
|
+
const destPath = join7(targetDir, componentPath);
|
|
5038
5190
|
const fs = await import("node:fs/promises");
|
|
5039
5191
|
const synced = [];
|
|
5040
5192
|
const queue = [{ dir: srcPath, relDir: "" }];
|
|
@@ -5048,7 +5200,7 @@ async function applyNamespaceSync(targetDir, component, lockfile) {
|
|
|
5048
5200
|
}
|
|
5049
5201
|
for (const entry of entries) {
|
|
5050
5202
|
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
5051
|
-
const fullSrcPath =
|
|
5203
|
+
const fullSrcPath = join7(dir2, entry.name);
|
|
5052
5204
|
if (entry.isDirectory()) {
|
|
5053
5205
|
queue.push({ dir: fullSrcPath, relDir: relPath });
|
|
5054
5206
|
continue;
|
|
@@ -5071,26 +5223,26 @@ function getComponentPath2(component) {
|
|
|
5071
5223
|
}
|
|
5072
5224
|
async function backupInstallation(targetDir) {
|
|
5073
5225
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
5074
|
-
const backupDir =
|
|
5226
|
+
const backupDir = join7(targetDir, `.omcustom-backup-${timestamp}`);
|
|
5075
5227
|
const fs = await import("node:fs/promises");
|
|
5076
5228
|
await ensureDirectory(backupDir);
|
|
5077
5229
|
const layout = getProviderLayout();
|
|
5078
5230
|
const dirsToBackup = [layout.rootDir, "guides"];
|
|
5079
5231
|
for (const dir2 of dirsToBackup) {
|
|
5080
|
-
const srcPath =
|
|
5232
|
+
const srcPath = join7(targetDir, dir2);
|
|
5081
5233
|
if (await fileExists(srcPath)) {
|
|
5082
|
-
const destPath =
|
|
5234
|
+
const destPath = join7(backupDir, dir2);
|
|
5083
5235
|
await copyDirectory(srcPath, destPath, { overwrite: true });
|
|
5084
5236
|
}
|
|
5085
5237
|
}
|
|
5086
|
-
const entryPath =
|
|
5238
|
+
const entryPath = join7(targetDir, layout.entryFile);
|
|
5087
5239
|
if (await fileExists(entryPath)) {
|
|
5088
|
-
await fs.copyFile(entryPath,
|
|
5240
|
+
await fs.copyFile(entryPath, join7(backupDir, layout.entryFile));
|
|
5089
5241
|
}
|
|
5090
5242
|
return backupDir;
|
|
5091
5243
|
}
|
|
5092
5244
|
async function loadCustomizationManifest(targetDir) {
|
|
5093
|
-
const manifestPath =
|
|
5245
|
+
const manifestPath = join7(targetDir, CUSTOMIZATION_MANIFEST_FILE);
|
|
5094
5246
|
if (await fileExists(manifestPath)) {
|
|
5095
5247
|
return readJsonFile(manifestPath);
|
|
5096
5248
|
}
|
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