oh-my-customcodex 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,7 +29,7 @@ oh-my-customcodex is built on two ideas:
29
29
 
30
30
  | Compile Concept | oh-my-customcodex |
31
31
  |----------------|-----------------|
32
- | Source code | `.codex/skills/` — reusable knowledge and workflows |
32
+ | Source code | `.agents/skills/` — reusable knowledge and workflows |
33
33
  | Build artifacts | `.codex/agents/` — executable specialists assembled from skills |
34
34
  | Compiler | `mgr-sauron` (R017) — structural verification and integrity |
35
35
  | Spec | `.codex/rules/` — constraints and build rules |
@@ -225,7 +225,7 @@ Key rules: R010 (orchestrator never writes files), R009 (parallel execution mand
225
225
 
226
226
  ---
227
227
 
228
- ### Guides (39)
228
+ ### Guides (40)
229
229
 
230
230
  Reference documentation covering best practices, architecture decisions, and integration patterns. Located in `guides/` at project root, covering topics from agent design to CI/CD to observability.
231
231
 
@@ -282,7 +282,11 @@ your-project/
282
282
  │ ├── specs/ # Extracted canonical specs
283
283
  │ ├── contexts/ # 4 shared context files
284
284
  │ └── ontology/ # Knowledge graph for RAG
285
- └── guides/ # 39 reference documents
285
+ <<<<<<< HEAD
286
+ └── guides/ # 40 reference documents
287
+ =======
288
+ └── guides/ # 40 reference documents
289
+ >>>>>>> origin/develop
286
290
  ```
287
291
 
288
292
  ---
package/dist/cli/index.js CHANGED
@@ -2653,6 +2653,9 @@ function getComponentPath(component, provider = "codex") {
2653
2653
  if (component === "guides") {
2654
2654
  return "guides";
2655
2655
  }
2656
+ if (provider === "codex" && component === "skills") {
2657
+ return ".agents/skills";
2658
+ }
2656
2659
  return `${layout.rootDir}/${component}`;
2657
2660
  }
2658
2661
  function getTemplateComponentPath(component, provider = "codex") {
@@ -2681,8 +2684,9 @@ var init_layout = __esm(() => {
2681
2684
  ".codex/hooks",
2682
2685
  ".codex/contexts",
2683
2686
  ".codex/agents",
2684
- ".codex/skills",
2685
2687
  ".codex/ontology",
2688
+ ".agents",
2689
+ ".agents/skills",
2686
2690
  "guides"
2687
2691
  ]
2688
2692
  };
@@ -3087,12 +3091,14 @@ var init_package = __esm(() => {
3087
3091
  workspaces: [
3088
3092
  "packages/*"
3089
3093
  ],
3090
- version: "0.2.0",
3094
+ version: "0.3.0",
3091
3095
  description: "Batteries-included agent harness on top of GPT Codex + OMX",
3092
3096
  type: "module",
3093
3097
  bin: {
3094
3098
  omcodex: "./dist/cli/index.js",
3095
- omcustom: "./dist/cli/index.js"
3099
+ omcustom: "./dist/cli/index.js",
3100
+ omcustomx: "./dist/cli/index.js",
3101
+ omcustomcodex: "./dist/cli/index.js"
3096
3102
  },
3097
3103
  main: "./dist/index.js",
3098
3104
  types: "./dist/index.d.ts",
@@ -3173,6 +3179,40 @@ var init_package = __esm(() => {
3173
3179
  };
3174
3180
  });
3175
3181
 
3182
+ // src/utils/cli-command-name.ts
3183
+ import { basename as basename2 } from "node:path";
3184
+ function normalizeCliCommandName(commandName) {
3185
+ const normalized = commandName?.trim().toLowerCase().replace(WINDOWS_SCRIPT_EXTENSIONS, "");
3186
+ return normalized && KNOWN_CLI_COMMANDS.has(normalized) ? normalized : DEFAULT_CLI_COMMAND;
3187
+ }
3188
+ function detectCliCommandName(argv = process.argv) {
3189
+ const invokedPath = argv[1];
3190
+ if (!invokedPath) {
3191
+ return DEFAULT_CLI_COMMAND;
3192
+ }
3193
+ const normalizedPath = invokedPath.replace(/\\/g, "/");
3194
+ return normalizeCliCommandName(basename2(normalizedPath));
3195
+ }
3196
+ function setActiveCliCommandName(commandName) {
3197
+ activeCliCommandName = normalizeCliCommandName(commandName);
3198
+ }
3199
+ function getActiveCliCommandName() {
3200
+ return activeCliCommandName;
3201
+ }
3202
+ function rewriteCliCommandReferences(text, commandName = getActiveCliCommandName()) {
3203
+ const normalizedCommandName = normalizeCliCommandName(commandName);
3204
+ if (normalizedCommandName === DEFAULT_CLI_COMMAND) {
3205
+ return text;
3206
+ }
3207
+ return text.replace(/(^|[^A-Za-z0-9_.-])omcodex(?=$|[^A-Za-z0-9_.-])/g, (_match, prefix) => `${prefix}${normalizedCommandName}`);
3208
+ }
3209
+ var DEFAULT_CLI_COMMAND = "omcodex", KNOWN_CLI_COMMANDS, WINDOWS_SCRIPT_EXTENSIONS, activeCliCommandName;
3210
+ var init_cli_command_name = __esm(() => {
3211
+ KNOWN_CLI_COMMANDS = new Set([DEFAULT_CLI_COMMAND, "omcustom", "omcustomx", "omcustomcodex"]);
3212
+ WINDOWS_SCRIPT_EXTENSIONS = /\.(cmd|exe|ps1|bat)$/i;
3213
+ activeCliCommandName = DEFAULT_CLI_COMMAND;
3214
+ });
3215
+
3176
3216
  // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/identity.js
3177
3217
  var require_identity = __commonJS((exports) => {
3178
3218
  var ALIAS = Symbol.for("yaml.alias");
@@ -10085,7 +10125,7 @@ __export(exports_projects, {
10085
10125
  default: () => projects_default
10086
10126
  });
10087
10127
  import { homedir as homedir3 } from "node:os";
10088
- import { basename as basename4, join as join9, sep as sep3 } from "node:path";
10128
+ import { basename as basename5, join as join9, sep as sep3 } from "node:path";
10089
10129
  async function readLockFile(projectDir) {
10090
10130
  const fs2 = await import("node:fs/promises");
10091
10131
  for (const lockFilePath of [
@@ -10145,7 +10185,7 @@ async function findProjects(options = {}) {
10145
10185
  if (Object.keys(registry.projects).length === 0) {
10146
10186
  const fallbackResults = await _findProjectsFromLockfiles(options, currentVersion);
10147
10187
  if (fallbackResults.length === 0 && !options.paths) {
10148
- process.stderr.write(" No projects in registry. Run `omcodex projects --migrate` to import existing projects.\n");
10188
+ process.stderr.write(rewriteCliCommandReferences(" No projects in registry. Run `omcodex projects --migrate` to import existing projects.\n"));
10149
10189
  }
10150
10190
  return fallbackResults;
10151
10191
  }
@@ -10157,7 +10197,7 @@ async function findProjects(options = {}) {
10157
10197
  if (!isUnderHome(projectPath, home))
10158
10198
  continue;
10159
10199
  results.push({
10160
- name: basename4(projectPath),
10200
+ name: basename5(projectPath),
10161
10201
  path: projectPath,
10162
10202
  version: entry.version || null,
10163
10203
  installedAt: entry.installedAt || null,
@@ -10183,7 +10223,7 @@ async function _scanDirForLockfiles(dir2, depth, seen, results, home, currentVer
10183
10223
  if (isUnderHome(dir2, home)) {
10184
10224
  const version = lockFile.version || lockFile.templateVersion || null;
10185
10225
  results.push({
10186
- name: basename4(dir2),
10226
+ name: basename5(dir2),
10187
10227
  path: dir2,
10188
10228
  version,
10189
10229
  installedAt: lockFile.installedAt || null,
@@ -10233,7 +10273,7 @@ function formatProjectsTable(projects, currentVersion) {
10233
10273
  if (projects.length === 0) {
10234
10274
  console.log(`
10235
10275
  oh-my-customcodex가 적용된 프로젝트를 찾을 수 없습니다.`);
10236
- console.log(" 레지스트리가 비어 있습니다. `omcodex projects --migrate`를 실행하여 기존 프로젝트를 가져오세요.\n");
10276
+ console.log(rewriteCliCommandReferences(" 레지스트리가 비어 있습니다. `omcodex projects --migrate`를 실행하여 기존 프로젝트를 가져오세요.\n"));
10237
10277
  return;
10238
10278
  }
10239
10279
  const nameWidth = Math.max(20, ...projects.map((p) => p.name.length));
@@ -10340,6 +10380,7 @@ var SCAN_SKIP_DIRS2, projects_default;
10340
10380
  var init_projects = __esm(() => {
10341
10381
  init_package();
10342
10382
  init_registry();
10383
+ init_cli_command_name();
10343
10384
  init_fs();
10344
10385
  SCAN_SKIP_DIRS2 = new Set(["node_modules", "dist", "build", ".git"]);
10345
10386
  projects_default = projectsCommand;
@@ -22737,7 +22778,10 @@ var init_dist17 = __esm(() => {
22737
22778
  });
22738
22779
 
22739
22780
  // src/cli/index.ts
22781
+ import { realpathSync } from "node:fs";
22740
22782
  import { createRequire as createRequire2 } from "node:module";
22783
+ import { resolve as resolve4 } from "node:path";
22784
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
22741
22785
 
22742
22786
  // node_modules/.bun/commander@14.0.2/node_modules/commander/esm.mjs
22743
22787
  var import__ = __toESM(require_commander(), 1);
@@ -25392,6 +25436,9 @@ var setDefaultNamespace = instance.setDefaultNamespace;
25392
25436
  var hasLoadedNamespace = instance.hasLoadedNamespace;
25393
25437
  var loadNamespaces = instance.loadNamespaces;
25394
25438
  var loadLanguages = instance.loadLanguages;
25439
+
25440
+ // src/i18n/index.ts
25441
+ init_cli_command_name();
25395
25442
  // src/i18n/locales/en.json
25396
25443
  var en_default = {
25397
25444
  common: {
@@ -26275,7 +26322,7 @@ async function initI18n(language) {
26275
26322
  }
26276
26323
  var i18n = {
26277
26324
  t(key, options) {
26278
- return instance.t(key, options);
26325
+ return rewriteCliCommandReferences(instance.t(key, options));
26279
26326
  },
26280
26327
  async changeLanguage(language) {
26281
26328
  await instance.changeLanguage(language);
@@ -26606,6 +26653,9 @@ async function maybeHandleSelfUpdateForInit(options) {
26606
26653
  runGlobalUpdate(packageName, latestVersion);
26607
26654
  }
26608
26655
 
26656
+ // src/cli/index.ts
26657
+ init_cli_command_name();
26658
+
26609
26659
  // src/cli/doctor.ts
26610
26660
  import { constants, promises as fs, readFileSync as readFileSync2 } from "node:fs";
26611
26661
  import path from "node:path";
@@ -27193,8 +27243,8 @@ async function checkAgents(targetDir, rootDir = ".codex") {
27193
27243
  fixable: false
27194
27244
  };
27195
27245
  }
27196
- async function checkSymlinks(targetDir, rootDir = ".codex") {
27197
- const skillsDir = path.join(targetDir, rootDir, "skills");
27246
+ async function checkSymlinks(targetDir, _rootDir = ".codex") {
27247
+ const skillsDir = path.join(targetDir, getComponentPath("skills"));
27198
27248
  const brokenSymlinks = [];
27199
27249
  if (await isDirectory(skillsDir)) {
27200
27250
  const skillSymlinks = await findRefsSymlinks(skillsDir);
@@ -27255,8 +27305,8 @@ async function checkIndexFiles(targetDir) {
27255
27305
  fixable: false
27256
27306
  };
27257
27307
  }
27258
- async function checkSkills(targetDir, rootDir = ".codex") {
27259
- const skillsDir = path.join(targetDir, rootDir, "skills");
27308
+ async function checkSkills(targetDir, _rootDir = ".codex") {
27309
+ const skillsDir = path.join(targetDir, getComponentPath("skills"));
27260
27310
  const exists2 = await isDirectory(skillsDir);
27261
27311
  if (!exists2) {
27262
27312
  return {
@@ -27479,7 +27529,7 @@ async function fixSingleIssue(check, targetDir, rootDir = ".codex") {
27479
27529
  const fixMap = {
27480
27530
  Rules: () => createMissingDirectory(path.join(targetDir, rootDir, "rules")),
27481
27531
  Agents: () => createMissingDirectory(path.join(targetDir, rootDir, "agents")),
27482
- Skills: () => createMissingDirectory(path.join(targetDir, rootDir, "skills")),
27532
+ Skills: () => createMissingDirectory(path.join(targetDir, getComponentPath("skills"))),
27483
27533
  Guides: () => createMissingDirectory(path.join(targetDir, "guides")),
27484
27534
  Hooks: () => createMissingDirectory(path.join(targetDir, rootDir, "hooks")),
27485
27535
  Contexts: () => createMissingDirectory(path.join(targetDir, rootDir, "contexts")),
@@ -27719,19 +27769,19 @@ import {
27719
27769
  rename,
27720
27770
  stat as stat2
27721
27771
  } from "node:fs/promises";
27722
- import { basename as basename3, join as join7 } from "node:path";
27772
+ import { basename as basename4, join as join7 } from "node:path";
27723
27773
 
27724
27774
  // src/core/file-preservation.ts
27725
27775
  init_fs();
27726
27776
  init_logger();
27727
- import { basename as basename2, join as join6 } from "node:path";
27777
+ import { basename as basename3, join as join6 } from "node:path";
27728
27778
  var DEFAULT_CRITICAL_FILES = ["settings.json", "settings.local.json"];
27729
27779
  var DEFAULT_CRITICAL_DIRECTORIES = ["agent-memory", "agent-memory-local"];
27730
27780
  var PROTECTED_FRAMEWORK_FILES = ["CLAUDE.md", "AGENTS.md"];
27731
27781
  var PROTECTED_RULE_PATTERNS = ["rules/MUST-*.md"];
27732
27782
  function isProtectedFile(relativePath) {
27733
- const basename3 = relativePath.split("/").pop() ?? "";
27734
- if (PROTECTED_FRAMEWORK_FILES.includes(basename3)) {
27783
+ const basename4 = relativePath.split("/").pop() ?? "";
27784
+ if (PROTECTED_FRAMEWORK_FILES.includes(basename4)) {
27735
27785
  return true;
27736
27786
  }
27737
27787
  for (const pattern of PROTECTED_RULE_PATTERNS) {
@@ -27840,10 +27890,10 @@ async function mergeJsonFile(preservedPath, targetPath) {
27840
27890
  const targetData = await readJsonFile(targetPath);
27841
27891
  const merged = deepMerge(targetData, preservedData);
27842
27892
  await writeJsonFile(targetPath, merged);
27843
- debug("preserve.merged_json", { file: basename2(targetPath) });
27893
+ debug("preserve.merged_json", { file: basename3(targetPath) });
27844
27894
  } else {
27845
27895
  await copyFile(preservedPath, targetPath);
27846
- debug("preserve.copied_json", { file: basename2(targetPath) });
27896
+ debug("preserve.copied_json", { file: basename3(targetPath) });
27847
27897
  }
27848
27898
  }
27849
27899
  function deepMerge(target, source) {
@@ -28517,7 +28567,7 @@ async function installEntryDoc(targetDir, language, overwrite = false) {
28517
28567
  return true;
28518
28568
  }
28519
28569
  async function backupExisting(sourcePath, backupDir) {
28520
- const name = basename3(sourcePath);
28570
+ const name = basename4(sourcePath);
28521
28571
  const backupPath = join7(backupDir, name);
28522
28572
  await rename(sourcePath, backupPath);
28523
28573
  return backupPath;
@@ -28525,6 +28575,9 @@ async function backupExisting(sourcePath, backupDir) {
28525
28575
  async function checkExistingPaths(targetDir) {
28526
28576
  const layout = getProviderLayout();
28527
28577
  const pathsToCheck = [layout.entryFile, layout.rootDir, "guides"];
28578
+ if (layout.provider === "codex") {
28579
+ pathsToCheck.push(".agents");
28580
+ }
28528
28581
  const existingPaths = [];
28529
28582
  for (const relativePath of pathsToCheck) {
28530
28583
  const fullPath = join7(targetDir, relativePath);
@@ -28649,51 +28702,101 @@ init_layout();
28649
28702
  init_registry();
28650
28703
  async function checkExistingInstallation(targetDir) {
28651
28704
  const layout = getProviderLayout();
28652
- const rootDir = join10(targetDir, layout.rootDir);
28653
- return fileExists(rootDir);
28705
+ const markers = [layout.entryFile, layout.rootDir];
28706
+ if (layout.provider === "codex") {
28707
+ markers.push(".agents");
28708
+ }
28709
+ for (const marker of markers) {
28710
+ if (await fileExists(join10(targetDir, marker))) {
28711
+ return true;
28712
+ }
28713
+ }
28714
+ return false;
28654
28715
  }
28655
- async function installFromSnapshot(targetDir, snapshotPath, options) {
28716
+ function getSnapshotPaths(snapshotPath) {
28717
+ const layout = getProviderLayout();
28718
+ return {
28719
+ layout,
28720
+ snapshotRuntime: join10(snapshotPath, layout.rootDir),
28721
+ snapshotSkills: join10(snapshotPath, getComponentPath("skills")),
28722
+ snapshotGuides: join10(snapshotPath, "guides"),
28723
+ snapshotEntry: join10(snapshotPath, layout.entryFile)
28724
+ };
28725
+ }
28726
+ function validateSnapshot(snapshotPath) {
28656
28727
  if (!existsSync2(snapshotPath)) {
28728
+ return { valid: false, error: `Snapshot path not found: ${snapshotPath}` };
28729
+ }
28730
+ const { layout, snapshotRuntime, snapshotSkills } = getSnapshotPaths(snapshotPath);
28731
+ if (!existsSync2(snapshotRuntime) && !existsSync2(snapshotSkills)) {
28657
28732
  return {
28658
- success: false,
28659
- message: i18n.t("cli.init.failed"),
28660
- errors: [`Snapshot path not found: ${snapshotPath}`]
28733
+ valid: false,
28734
+ error: `Invalid snapshot: missing ${layout.rootDir}/ or ${getComponentPath("skills")} in ${snapshotPath}`
28661
28735
  };
28662
28736
  }
28663
- const layout = getProviderLayout();
28664
- const snapshotClaude = join10(snapshotPath, layout.rootDir);
28665
- if (!existsSync2(snapshotClaude)) {
28737
+ return { valid: true };
28738
+ }
28739
+ async function backupExistingInstallationForSnapshot(targetDir, snapshotPath) {
28740
+ const { layout } = getSnapshotPaths(snapshotPath);
28741
+ const exists2 = await checkExistingInstallation(targetDir);
28742
+ if (!exists2)
28743
+ return;
28744
+ console.log(i18n.t("cli.init.exists", { rootDir: layout.rootDir }));
28745
+ console.log(i18n.t("cli.init.backing_up"));
28746
+ const backupDir = join10(targetDir, `${layout.backupDirPrefix}${new Date().toISOString().replace(/[:.]/g, "-").slice(0, -1)}`);
28747
+ if (existsSync2(join10(targetDir, layout.rootDir))) {
28748
+ await cp(join10(targetDir, layout.rootDir), join10(backupDir, layout.rootDir), { recursive: true });
28749
+ }
28750
+ if (existsSync2(join10(targetDir, ".agents"))) {
28751
+ await cp(join10(targetDir, ".agents"), join10(backupDir, ".agents"), { recursive: true });
28752
+ }
28753
+ if (existsSync2(join10(targetDir, layout.entryFile))) {
28754
+ await copyFile2(join10(targetDir, layout.entryFile), join10(backupDir, layout.entryFile));
28755
+ }
28756
+ if (existsSync2(join10(targetDir, "guides"))) {
28757
+ await cp(join10(targetDir, "guides"), join10(backupDir, "guides"), { recursive: true });
28758
+ }
28759
+ console.log(` Backed up to: ${backupDir}`);
28760
+ }
28761
+ async function copySnapshotIntoTarget(targetDir, snapshotPath) {
28762
+ const { layout, snapshotRuntime, snapshotSkills, snapshotGuides, snapshotEntry } = getSnapshotPaths(snapshotPath);
28763
+ if (existsSync2(snapshotRuntime)) {
28764
+ await cp(snapshotRuntime, join10(targetDir, layout.rootDir), {
28765
+ recursive: true,
28766
+ force: true
28767
+ });
28768
+ }
28769
+ if (existsSync2(snapshotSkills)) {
28770
+ await cp(snapshotSkills, join10(targetDir, getComponentPath("skills")), {
28771
+ recursive: true,
28772
+ force: true
28773
+ });
28774
+ }
28775
+ if (existsSync2(snapshotGuides)) {
28776
+ await cp(snapshotGuides, join10(targetDir, "guides"), {
28777
+ recursive: true,
28778
+ force: true
28779
+ });
28780
+ }
28781
+ if (existsSync2(snapshotEntry)) {
28782
+ await copyFile2(snapshotEntry, join10(targetDir, layout.entryFile));
28783
+ }
28784
+ }
28785
+ async function installFromSnapshot(targetDir, snapshotPath, options) {
28786
+ const snapshotValidation = validateSnapshot(snapshotPath);
28787
+ if (!snapshotValidation.valid) {
28666
28788
  return {
28667
28789
  success: false,
28668
28790
  message: i18n.t("cli.init.failed"),
28669
- errors: [`Invalid snapshot: missing ${layout.rootDir}/ directory in ${snapshotPath}`]
28791
+ errors: [snapshotValidation.error]
28670
28792
  };
28671
28793
  }
28672
28794
  console.log(`Installing from snapshot: ${snapshotPath}`);
28673
28795
  try {
28674
- const exists2 = await checkExistingInstallation(targetDir);
28675
- if (exists2 && !options.force) {
28676
- console.log(i18n.t("cli.init.exists", { rootDir: layout.rootDir }));
28677
- console.log(i18n.t("cli.init.backing_up"));
28678
- const backupDir = join10(targetDir, `${layout.backupDirPrefix}${new Date().toISOString().replace(/[:.]/g, "-").slice(0, -1)}`);
28679
- await cp(join10(targetDir, layout.rootDir), backupDir, { recursive: true });
28680
- console.log(` Backed up to: ${backupDir}`);
28681
- }
28682
- await cp(snapshotClaude, join10(targetDir, layout.rootDir), {
28683
- recursive: true,
28684
- force: true
28685
- });
28686
- const snapshotGuides = join10(snapshotPath, "guides");
28687
- if (existsSync2(snapshotGuides)) {
28688
- await cp(snapshotGuides, join10(targetDir, "guides"), {
28689
- recursive: true,
28690
- force: true
28691
- });
28692
- }
28693
- const snapshotEntry = join10(snapshotPath, layout.entryFile);
28694
- if (existsSync2(snapshotEntry)) {
28695
- await copyFile2(snapshotEntry, join10(targetDir, layout.entryFile));
28796
+ if (!options.force) {
28797
+ await backupExistingInstallationForSnapshot(targetDir, snapshotPath);
28696
28798
  }
28799
+ await copySnapshotIntoTarget(targetDir, snapshotPath);
28697
28800
  try {
28698
28801
  const existing = await readLockFile(targetDir);
28699
28802
  await writeLockFile(targetDir, package_default.version, existing);
@@ -29700,8 +29803,16 @@ async function runInitWizard(options) {
29700
29803
  // src/cli/init.ts
29701
29804
  async function checkExistingInstallation2(targetDir) {
29702
29805
  const layout = getProviderLayout();
29703
- const rootDir = join11(targetDir, layout.rootDir);
29704
- return fileExists(rootDir);
29806
+ const markers = [layout.entryFile, layout.rootDir];
29807
+ if (layout.provider === "codex") {
29808
+ markers.push(".agents");
29809
+ }
29810
+ for (const marker of markers) {
29811
+ if (await fileExists(join11(targetDir, marker))) {
29812
+ return true;
29813
+ }
29814
+ }
29815
+ return false;
29705
29816
  }
29706
29817
  var PROVIDER_SUBDIR_COMPONENTS = new Set([
29707
29818
  "rules",
@@ -29712,13 +29823,8 @@ var PROVIDER_SUBDIR_COMPONENTS = new Set([
29712
29823
  "ontology"
29713
29824
  ]);
29714
29825
  function componentToPath(targetDir, component) {
29715
- if (component === "entry-md") {
29716
- const layout = getProviderLayout();
29717
- return join11(targetDir, layout.entryFile);
29718
- }
29719
- if (PROVIDER_SUBDIR_COMPONENTS.has(component)) {
29720
- const layout = getProviderLayout();
29721
- return join11(targetDir, layout.rootDir, component);
29826
+ if (component === "entry-md" || PROVIDER_SUBDIR_COMPONENTS.has(component)) {
29827
+ return join11(targetDir, getComponentPath(component));
29722
29828
  }
29723
29829
  return join11(targetDir, component);
29724
29830
  }
@@ -29845,7 +29951,7 @@ async function initCommand(options) {
29845
29951
  }
29846
29952
 
29847
29953
  // src/cli/list.ts
29848
- import { basename as basename5, dirname as dirname3, join as join12, relative as relative3 } from "node:path";
29954
+ import { basename as basename6, dirname as dirname3, join as join12, relative as relative3 } from "node:path";
29849
29955
  init_layout();
29850
29956
  init_fs();
29851
29957
  var ALLOWED_TOP_LEVEL_KEYS = new Set(["name", "type", "description", "version", "category"]);
@@ -29893,7 +29999,7 @@ function parseYamlMetadata(content) {
29893
29999
  return result;
29894
30000
  }
29895
30001
  function extractAgentTypeFromFilename(filename) {
29896
- const name = basename5(filename, ".md");
30002
+ const name = basename6(filename, ".md");
29897
30003
  const prefixMap = {
29898
30004
  lang: "language",
29899
30005
  be: "backend",
@@ -29911,9 +30017,13 @@ function extractAgentTypeFromFilename(filename) {
29911
30017
  return prefixMap[prefix] || "unknown";
29912
30018
  }
29913
30019
  function extractSkillCategoryFromPath(skillPath, baseDir, rootDir) {
29914
- const relativePath = relative3(join12(baseDir, rootDir, "skills"), skillPath);
30020
+ const provider = rootDir === ".claude" ? "claude" : "codex";
30021
+ const relativePath = relative3(join12(baseDir, getComponentPath("skills", provider)), skillPath);
29915
30022
  const parts = relativePath.split("/").filter(Boolean);
29916
- return parts[0] || "unknown";
30023
+ if (parts.length <= 1) {
30024
+ return "general";
30025
+ }
30026
+ return parts[0];
29917
30027
  }
29918
30028
  function extractGuideCategoryFromPath(guidePath, baseDir) {
29919
30029
  const relativePath = relative3(join12(baseDir, "guides"), guidePath);
@@ -29921,7 +30031,7 @@ function extractGuideCategoryFromPath(guidePath, baseDir) {
29921
30031
  return parts[0] || "unknown";
29922
30032
  }
29923
30033
  function extractRulePriorityFromFilename(filename) {
29924
- const name = basename5(filename, ".md");
30034
+ const name = basename6(filename, ".md");
29925
30035
  const parts = name.split("-");
29926
30036
  return parts[0] || "unknown";
29927
30037
  }
@@ -30019,8 +30129,8 @@ async function getAgents(targetDir, rootDir = ".codex", config) {
30019
30129
  const customAgentPaths = new Set(customComponents.filter((c) => c.type === "agent").map((c) => c.path));
30020
30130
  const agentMdFiles = await listFiles(agentsDir, { recursive: false, pattern: "*.md" });
30021
30131
  const agents = await Promise.all(agentMdFiles.map(async (agentMdPath) => {
30022
- const filename = basename5(agentMdPath);
30023
- const name = basename5(filename, ".md");
30132
+ const filename = basename6(agentMdPath);
30133
+ const name = basename6(filename, ".md");
30024
30134
  const description = await tryExtractMarkdownDescription(agentMdPath);
30025
30135
  const relativePath = relative3(targetDir, agentMdPath);
30026
30136
  return {
@@ -30038,7 +30148,8 @@ async function getAgents(targetDir, rootDir = ".codex", config) {
30038
30148
  }
30039
30149
  }
30040
30150
  async function getSkills(targetDir, rootDir = ".codex", config) {
30041
- const skillsDir = join12(targetDir, rootDir, "skills");
30151
+ const provider = rootDir === ".claude" ? "claude" : "codex";
30152
+ const skillsDir = join12(targetDir, getComponentPath("skills", provider));
30042
30153
  if (!await fileExists(skillsDir))
30043
30154
  return [];
30044
30155
  try {
@@ -30052,7 +30163,7 @@ async function getSkills(targetDir, rootDir = ".codex", config) {
30052
30163
  const { description, version } = await tryReadIndexYamlMetadata(indexYamlPath);
30053
30164
  const relativePath = relative3(targetDir, skillDir);
30054
30165
  return {
30055
- name: basename5(skillDir),
30166
+ name: basename6(skillDir),
30056
30167
  type: "skill",
30057
30168
  category: extractSkillCategoryFromPath(skillDir, targetDir, rootDir),
30058
30169
  path: relativePath,
@@ -30079,7 +30190,7 @@ async function getGuides(targetDir, config) {
30079
30190
  const description = await tryExtractMarkdownDescription(guideMdPath, { maxLength: 100 });
30080
30191
  const relativePath = relative3(targetDir, guideMdPath);
30081
30192
  return {
30082
- name: basename5(guideMdPath, ".md"),
30193
+ name: basename6(guideMdPath, ".md"),
30083
30194
  type: "guide",
30084
30195
  category: extractGuideCategoryFromPath(guideMdPath, targetDir),
30085
30196
  path: relativePath,
@@ -30103,13 +30214,13 @@ async function getRules(targetDir, rootDir = ".codex", config) {
30103
30214
  const customRulePaths = new Set(customComponents.filter((c) => c.type === "rule").map((c) => c.path));
30104
30215
  const ruleMdFiles = await listFiles(rulesDir, { recursive: false, pattern: "*.md" });
30105
30216
  const rules = await Promise.all(ruleMdFiles.map(async (ruleMdPath) => {
30106
- const filename = basename5(ruleMdPath);
30217
+ const filename = basename6(ruleMdPath);
30107
30218
  const description = await tryExtractMarkdownDescription(ruleMdPath, {
30108
30219
  cleanFormatting: true
30109
30220
  });
30110
30221
  const relativePath = relative3(targetDir, ruleMdPath);
30111
30222
  return {
30112
- name: basename5(ruleMdPath, ".md"),
30223
+ name: basename6(ruleMdPath, ".md"),
30113
30224
  type: extractRulePriorityFromFilename(filename),
30114
30225
  path: relativePath,
30115
30226
  description,
@@ -30175,7 +30286,7 @@ async function getHooks(targetDir, rootDir = ".codex") {
30175
30286
  const hookYamls = await listFiles(hooksDir, { recursive: true, pattern: "*.yaml" });
30176
30287
  const allFiles = [...hookFiles, ...hookConfigs, ...hookYamls];
30177
30288
  return allFiles.map((hookPath) => ({
30178
- name: basename5(hookPath),
30289
+ name: basename6(hookPath),
30179
30290
  type: "hook",
30180
30291
  path: relative3(targetDir, hookPath)
30181
30292
  })).sort((a, b) => a.name.localeCompare(b.name));
@@ -30195,7 +30306,7 @@ async function getContexts(targetDir, rootDir = ".codex") {
30195
30306
  const ext = ctxPath.endsWith(".md") ? ".md" : ".yaml";
30196
30307
  const description = ext === ".md" ? await tryExtractMarkdownDescription(ctxPath, { maxLength: 100 }) : undefined;
30197
30308
  return {
30198
- name: basename5(ctxPath, ext),
30309
+ name: basename6(ctxPath, ext),
30199
30310
  type: "context",
30200
30311
  path: relative3(targetDir, ctxPath),
30201
30312
  description
@@ -30883,6 +30994,7 @@ async function exportSnapshot(targetDir, outputPath) {
30883
30994
  const detection = await detectProvider({ targetDir });
30884
30995
  const layout = getProviderLayout(detection.provider);
30885
30996
  const runtimeDir = join16(targetDir, layout.rootDir);
30997
+ const skillsDir = join16(targetDir, getComponentPath("skills", detection.provider));
30886
30998
  const guidesDir = join16(targetDir, "guides");
30887
30999
  if (!existsSync4(runtimeDir)) {
30888
31000
  return { success: false, exportPath: outputPath, fileCount: 0 };
@@ -30893,6 +31005,12 @@ async function exportSnapshot(targetDir, outputPath) {
30893
31005
  recursive: true,
30894
31006
  filter: isExportable
30895
31007
  });
31008
+ if (existsSync4(skillsDir)) {
31009
+ await cp2(skillsDir, join16(outputPath, getComponentPath("skills", detection.provider)), {
31010
+ recursive: true,
31011
+ filter: isExportable
31012
+ });
31013
+ }
30896
31014
  if (existsSync4(guidesDir)) {
30897
31015
  await cp2(guidesDir, join16(outputPath, "guides"), { recursive: true });
30898
31016
  }
@@ -30905,6 +31023,7 @@ async function exportSnapshot(targetDir, outputPath) {
30905
31023
  }
30906
31024
 
30907
31025
  // src/cli/sync.ts
31026
+ init_cli_command_name();
30908
31027
  async function runExport(targetDir, outputPath) {
30909
31028
  const result = await exportSnapshot(targetDir, resolve3(outputPath));
30910
31029
  const layout = getProviderLayout();
@@ -30915,7 +31034,7 @@ Export failed — no ${layout.rootDir}/ directory found in current project.`);
30915
31034
  }
30916
31035
  console.log(`
30917
31036
  Snapshot exported: ${result.exportPath} (${result.fileCount} files)`);
30918
- console.log(`Team members can install with: omcodex init --from-snapshot ${result.exportPath}`);
31037
+ console.log(rewriteCliCommandReferences(`Team members can install with: omcodex init --from-snapshot ${result.exportPath}`));
30919
31038
  }
30920
31039
  function printDriftDetails(result) {
30921
31040
  if (result.unchanged > 0) {
@@ -30943,8 +31062,8 @@ function printDriftDetails(result) {
30943
31062
  async function runCheck(targetDir, options) {
30944
31063
  const result = await syncCheck(targetDir, { reference: options.reference });
30945
31064
  if (!result.referenceVersion) {
30946
- console.error(`
30947
- No lockfile found. Run omcodex init first.`);
31065
+ console.error(rewriteCliCommandReferences(`
31066
+ No lockfile found. Run omcodex init first.`));
30948
31067
  process.exit(1);
30949
31068
  }
30950
31069
  const label = options.reference ? `external snapshot at ${options.reference}` : `lockfile (v${result.referenceVersion})`;
@@ -31733,6 +31852,9 @@ function getComponentPath2(component) {
31733
31852
  if (component === "guides") {
31734
31853
  return "guides";
31735
31854
  }
31855
+ if (layout.provider === "codex" && component === "skills") {
31856
+ return ".agents/skills";
31857
+ }
31736
31858
  return `${layout.rootDir}/${component}`;
31737
31859
  }
31738
31860
  async function backupInstallation(targetDir) {
@@ -31742,6 +31864,9 @@ async function backupInstallation(targetDir) {
31742
31864
  await ensureDirectory(backupDir);
31743
31865
  const layout = getProviderLayout();
31744
31866
  const dirsToBackup = [layout.rootDir, "guides"];
31867
+ if (layout.provider === "codex") {
31868
+ dirsToBackup.push(".agents");
31869
+ }
31745
31870
  for (const dir2 of dirsToBackup) {
31746
31871
  const srcPath = join17(targetDir, dir2);
31747
31872
  if (await fileExists(srcPath)) {
@@ -32057,9 +32182,9 @@ async function webOpenCommand(options) {
32057
32182
  // src/cli/index.ts
32058
32183
  var require2 = createRequire2(import.meta.url);
32059
32184
  var packageJson = require2("../../package.json");
32060
- function createProgram() {
32185
+ function createProgram(commandName = getActiveCliCommandName()) {
32061
32186
  const program2 = new Command;
32062
- program2.name("omcodex").description(i18n.t("cli.description")).version(packageJson.version, "-v, --version", i18n.t("cli.versionOption")).option("--skip-version-check", "Skip CLI version pre-flight check");
32187
+ program2.name(commandName).description(i18n.t("cli.description")).version(packageJson.version, "-v, --version", i18n.t("cli.versionOption")).option("--skip-version-check", "Skip CLI version pre-flight check");
32063
32188
  program2.command("init").description(i18n.t("cli.init.description")).option("-l, --lang <language>", i18n.t("cli.init.langOption")).option("--domain <domain>", "Install only agents/skills for specific domain (backend, frontend, data-engineering, devops)").option("--yes", "Skip interactive wizard, use defaults").option("--from-snapshot <path>", "Install from a pre-configured team snapshot directory").action(async (options) => {
32064
32189
  await initCommand(options);
32065
32190
  });
@@ -32096,11 +32221,11 @@ function createProgram() {
32096
32221
  web.action(async () => {
32097
32222
  await webStatusCommand();
32098
32223
  });
32099
- program2.command("serve").description("(Deprecated) Start the Web UI server — use `omcodex web start` instead").option("-p, --port <port>", i18n.t("cli.web.start.portOption"), "4321").option("--foreground", i18n.t("cli.web.start.foregroundOption")).action(async (options) => {
32224
+ program2.command("serve").description(`(Deprecated) Start the Web UI server — use \`${commandName} web start\` instead`).option("-p, --port <port>", i18n.t("cli.web.start.portOption"), "4321").option("--foreground", i18n.t("cli.web.start.foregroundOption")).action(async (options) => {
32100
32225
  console.warn(i18n.t("cli.web.deprecated.serve"));
32101
32226
  await serveCommand(options);
32102
32227
  });
32103
- program2.command("serve-stop").description("(Deprecated) Stop the Web UI server — use `omcodex web stop` instead").action(async () => {
32228
+ program2.command("serve-stop").description(`(Deprecated) Stop the Web UI server — use \`${commandName} web stop\` instead`).action(async () => {
32104
32229
  console.warn(i18n.t("cli.web.deprecated.serveStop"));
32105
32230
  await serveStopCommand();
32106
32231
  });
@@ -32149,15 +32274,29 @@ function createProgram() {
32149
32274
  return program2;
32150
32275
  }
32151
32276
  async function main() {
32277
+ setActiveCliCommandName(detectCliCommandName());
32152
32278
  const lang = detectLanguage();
32153
32279
  await initI18n(lang);
32154
32280
  const program2 = createProgram();
32155
32281
  await program2.parseAsync(process.argv);
32156
32282
  }
32157
- main().catch((error2) => {
32158
- console.error(i18n.t("cli.error.unexpected"), error2);
32159
- process.exit(1);
32160
- });
32283
+ function isDirectExecution() {
32284
+ const argvPath = process.argv[1];
32285
+ if (!argvPath) {
32286
+ return false;
32287
+ }
32288
+ try {
32289
+ return realpathSync(argvPath) === realpathSync(fileURLToPath3(import.meta.url));
32290
+ } catch {
32291
+ return resolve4(argvPath) === resolve4(fileURLToPath3(import.meta.url));
32292
+ }
32293
+ }
32294
+ if (isDirectExecution()) {
32295
+ main().catch((error2) => {
32296
+ console.error(i18n.t("cli.error.unexpected"), error2);
32297
+ process.exit(1);
32298
+ });
32299
+ }
32161
32300
  export {
32162
32301
  main,
32163
32302
  createProgram
package/dist/index.js CHANGED
@@ -538,6 +538,9 @@ function getComponentPath(component, provider = "codex") {
538
538
  if (component === "guides") {
539
539
  return "guides";
540
540
  }
541
+ if (provider === "codex" && component === "skills") {
542
+ return ".agents/skills";
543
+ }
541
544
  return `${layout.rootDir}/${component}`;
542
545
  }
543
546
  function getTemplateComponentPath(component, provider = "codex") {
@@ -566,8 +569,9 @@ var init_layout = __esm(() => {
566
569
  ".codex/hooks",
567
570
  ".codex/contexts",
568
571
  ".codex/agents",
569
- ".codex/skills",
570
572
  ".codex/ontology",
573
+ ".agents",
574
+ ".agents/skills",
571
575
  "guides"
572
576
  ]
573
577
  };
@@ -2084,6 +2088,9 @@ async function backupExisting(sourcePath, backupDir) {
2084
2088
  async function checkExistingPaths(targetDir) {
2085
2089
  const layout = getProviderLayout();
2086
2090
  const pathsToCheck = [layout.entryFile, layout.rootDir, "guides"];
2091
+ if (layout.provider === "codex") {
2092
+ pathsToCheck.push(".agents");
2093
+ }
2087
2094
  const existingPaths = [];
2088
2095
  for (const relativePath of pathsToCheck) {
2089
2096
  const fullPath = join5(targetDir, relativePath);
@@ -2173,12 +2180,14 @@ var package_default = {
2173
2180
  workspaces: [
2174
2181
  "packages/*"
2175
2182
  ],
2176
- version: "0.2.0",
2183
+ version: "0.3.0",
2177
2184
  description: "Batteries-included agent harness on top of GPT Codex + OMX",
2178
2185
  type: "module",
2179
2186
  bin: {
2180
2187
  omcodex: "./dist/cli/index.js",
2181
- omcustom: "./dist/cli/index.js"
2188
+ omcustom: "./dist/cli/index.js",
2189
+ omcustomx: "./dist/cli/index.js",
2190
+ omcustomcodex: "./dist/cli/index.js"
2182
2191
  },
2183
2192
  main: "./dist/index.js",
2184
2193
  types: "./dist/index.d.ts",
@@ -4669,12 +4678,32 @@ var setDefaultNamespace = instance.setDefaultNamespace;
4669
4678
  var hasLoadedNamespace = instance.hasLoadedNamespace;
4670
4679
  var loadNamespaces = instance.loadNamespaces;
4671
4680
  var loadLanguages = instance.loadLanguages;
4681
+
4682
+ // src/utils/cli-command-name.ts
4683
+ var DEFAULT_CLI_COMMAND = "omcodex";
4684
+ var KNOWN_CLI_COMMANDS = new Set([DEFAULT_CLI_COMMAND, "omcustom", "omcustomx", "omcustomcodex"]);
4685
+ var WINDOWS_SCRIPT_EXTENSIONS = /\.(cmd|exe|ps1|bat)$/i;
4686
+ var activeCliCommandName = DEFAULT_CLI_COMMAND;
4687
+ function normalizeCliCommandName(commandName) {
4688
+ const normalized = commandName?.trim().toLowerCase().replace(WINDOWS_SCRIPT_EXTENSIONS, "");
4689
+ return normalized && KNOWN_CLI_COMMANDS.has(normalized) ? normalized : DEFAULT_CLI_COMMAND;
4690
+ }
4691
+ function getActiveCliCommandName() {
4692
+ return activeCliCommandName;
4693
+ }
4694
+ function rewriteCliCommandReferences(text, commandName = getActiveCliCommandName()) {
4695
+ const normalizedCommandName = normalizeCliCommandName(commandName);
4696
+ if (normalizedCommandName === DEFAULT_CLI_COMMAND) {
4697
+ return text;
4698
+ }
4699
+ return text.replace(/(^|[^A-Za-z0-9_.-])omcodex(?=$|[^A-Za-z0-9_.-])/g, (_match, prefix) => `${prefix}${normalizedCommandName}`);
4700
+ }
4672
4701
  // src/i18n/types.ts
4673
4702
  var DEFAULT_LANGUAGE2 = "en";
4674
4703
  // src/i18n/index.ts
4675
4704
  var i18n = {
4676
4705
  t(key, options) {
4677
- return instance.t(key, options);
4706
+ return rewriteCliCommandReferences(instance.t(key, options));
4678
4707
  },
4679
4708
  async changeLanguage(language) {
4680
4709
  await instance.changeLanguage(language);
@@ -5458,6 +5487,9 @@ function getComponentPath2(component) {
5458
5487
  if (component === "guides") {
5459
5488
  return "guides";
5460
5489
  }
5490
+ if (layout.provider === "codex" && component === "skills") {
5491
+ return ".agents/skills";
5492
+ }
5461
5493
  return `${layout.rootDir}/${component}`;
5462
5494
  }
5463
5495
  async function backupInstallation(targetDir) {
@@ -5467,6 +5499,9 @@ async function backupInstallation(targetDir) {
5467
5499
  await ensureDirectory(backupDir);
5468
5500
  const layout = getProviderLayout();
5469
5501
  const dirsToBackup = [layout.rootDir, "guides"];
5502
+ if (layout.provider === "codex") {
5503
+ dirsToBackup.push(".agents");
5504
+ }
5470
5505
  for (const dir2 of dirsToBackup) {
5471
5506
  const srcPath = join8(targetDir, dir2);
5472
5507
  if (await fileExists(srcPath)) {
package/package.json CHANGED
@@ -3,12 +3,14 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "0.2.0",
6
+ "version": "0.3.0",
7
7
  "description": "Batteries-included agent harness on top of GPT Codex + OMX",
8
8
  "type": "module",
9
9
  "bin": {
10
10
  "omcodex": "./dist/cli/index.js",
11
- "omcustom": "./dist/cli/index.js"
11
+ "omcustom": "./dist/cli/index.js",
12
+ "omcustomx": "./dist/cli/index.js",
13
+ "omcustomcodex": "./dist/cli/index.js"
12
14
  },
13
15
  "main": "./dist/index.js",
14
16
  "types": "./dist/index.d.ts",
@@ -105,6 +105,10 @@
105
105
  {
106
106
  "matcher": "tool == \"Task\" || tool == \"Agent\"",
107
107
  "hooks": [
108
+ {
109
+ "type": "command",
110
+ "command": "bash .codex/hooks/scripts/agent-mode-guard.sh"
111
+ },
108
112
  {
109
113
  "type": "command",
110
114
  "command": "#!/bin/bash\ninput=$(cat)\nagent_type=$(echo \"$input\" | jq -r '.tool_input.subagent_type // \"unknown\"')\nmodel=$(echo \"$input\" | jq -r '.tool_input.model // \"inherit\"')\ndesc=$(echo \"$input\" | jq -r '.tool_input.description // \"\"' | head -c 40)\nresume=$(echo \"$input\" | jq -r '.tool_input.resume // empty')\nif [ -n \"$resume\" ]; then\n echo \"─── [Resume] ${agent_type}:${model} | ${desc} ───\" >&2\nelse\n echo \"─── [Spawn] ${agent_type}:${model} | ${desc} ───\" >&2\nfi\necho \"$input\""
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+ # Agent mode guard
3
+ # Blocks Agent/Task spawns that omit mode:"bypassPermissions".
4
+ # This closes the gap where frontmatter permissionMode is ignored unless the
5
+ # spawn call sets mode explicitly.
6
+
7
+ set -euo pipefail
8
+
9
+ command -v jq >/dev/null 2>&1 || exit 0
10
+
11
+ input=$(cat)
12
+ mode=$(echo "$input" | jq -r '.tool_input.mode // ""')
13
+
14
+ if [ "$mode" != "bypassPermissions" ]; then
15
+ echo "[Hook] BLOCKED: Agent/Task spawn missing required mode: \"bypassPermissions\"" >&2
16
+ echo "[Hook] Saw mode: ${mode:-<missing>}" >&2
17
+ echo "[Hook] The Agent tool defaults to acceptEdits and will trigger permission prompts." >&2
18
+ echo "[Hook] Fix the spawn call by adding: mode: \"bypassPermissions\"" >&2
19
+ exit 2
20
+ fi
21
+
22
+ echo "$input"
@@ -115,11 +115,12 @@ project/
115
115
  +-- AGENTS.md # 진입점
116
116
  +-- .codex/
117
117
  | +-- agents/ # 서브에이전트 정의 (48 파일)
118
- | +-- skills/ # 스킬 (109 디렉토리)
119
118
  | +-- rules/ # 전역 규칙 (R000-R022)
120
119
  | +-- hooks/ # 훅 스크립트 (보안, 검증, HUD)
121
120
  | +-- contexts/ # 컨텍스트 파일 (ecomode)
122
- +-- guides/ # 레퍼런스 문서 (38 토픽)
121
+ +-- .agents/
122
+ | +-- skills/ # 스킬 (109 디렉토리)
123
+ +-- guides/ # 레퍼런스 문서 (39 토픽)
123
124
  ```
124
125
 
125
126
  ## 오케스트레이션
@@ -149,7 +150,7 @@ oh-my-customcodex는 소프트웨어 컴파일과 동일한 구조를 따릅니
149
150
 
150
151
  | 컴파일 개념 | oh-my-customcodex 매핑 | 역할 |
151
152
  |------------|----------------------|------|
152
- | Source code | `.codex/skills/` | 재사용 가능한 지식과 워크플로우 정의 |
153
+ | Source code | `.agents/skills/` | 재사용 가능한 지식과 워크플로우 정의 |
153
154
  | Build artifacts | `.codex/agents/` | 스킬을 조합한 실행 가능한 전문가 |
154
155
  | Compiler | `mgr-sauron` (R017) | 구조 검증 및 정합성 보장 |
155
156
  | Spec | `.codex/rules/` | 빌드 규칙과 제약 조건 |
@@ -136,7 +136,7 @@ project/
136
136
  | +-- rules/ # Global rules (22 files)
137
137
  | +-- hooks/ # Hook scripts (security, validation, HUD)
138
138
  | +-- contexts/ # Context files (4 files)
139
- +-- guides/ # Reference docs (38 topics)
139
+ +-- guides/ # Reference docs (39 topics)
140
140
  ```
141
141
 
142
142
  ## Orchestration
@@ -136,7 +136,7 @@ project/
136
136
  | +-- rules/ # 전역 규칙 (22 파일)
137
137
  | +-- hooks/ # 훅 스크립트 (보안, 검증, HUD)
138
138
  | +-- contexts/ # 컨텍스트 파일 (4 파일)
139
- +-- guides/ # 레퍼런스 문서 (38 토픽)
139
+ +-- guides/ # 레퍼런스 문서 (39 토픽)
140
140
  ```
141
141
 
142
142
  ## 오케스트레이션
@@ -106,8 +106,8 @@ That keeps visual comparisons honest and makes "taste memory" reusable.
106
106
 
107
107
  ## Related Surfaces
108
108
 
109
- - `.codex/skills/product-strategy/SKILL.md`
110
- - `.codex/skills/design-shotgun/SKILL.md`
111
- - `.codex/skills/playwright-compress/SKILL.md`
109
+ - `.agents/skills/product-strategy/SKILL.md`
110
+ - `.agents/skills/design-shotgun/SKILL.md`
111
+ - `.agents/skills/playwright-compress/SKILL.md`
112
112
  - `guides/web-scraping/README.md`
113
113
  - `packages/serve/playwright.config.ts`
@@ -57,6 +57,14 @@ guides:
57
57
  origin: docs.oracle.com
58
58
  url: https://docs.oracle.com/en/java/javase/21/
59
59
 
60
+ - name: java
61
+ description: Java 25 LTS language reference and modern feature documentation
62
+ path: ./java/
63
+ source:
64
+ type: external
65
+ origin: docs.oracle.com
66
+ url: https://docs.oracle.com/en/java/javase/25/
67
+
60
68
  - name: python
61
69
  description: Python reference from PEP 8 and PEP 20
62
70
  path: ./python/
@@ -0,0 +1,29 @@
1
+ # Java Guide
2
+
3
+ metadata:
4
+ name: java
5
+ description: Java 25 LTS language reference and modern feature documentation
6
+
7
+ source:
8
+ type: external
9
+ origin: docs.oracle.com
10
+ urls:
11
+ - https://docs.oracle.com/en/java/javase/25/
12
+ - https://openjdk.org/projects/loom/
13
+ - https://openjdk.org/jeps/440
14
+ - https://openjdk.org/jeps/441
15
+ - https://openjdk.org/jeps/444
16
+ - https://google.github.io/styleguide/javaguide.html
17
+ last_fetched: "2026-04-20"
18
+
19
+ documents:
20
+ - name: modern-java
21
+ path: ./modern-java.md
22
+ description: Java 25 LTS modern features (Virtual Threads, Pattern Matching, Records, Sealed Classes)
23
+
24
+ - name: java-style-guide
25
+ path: ./java-style-guide.md
26
+ description: Google Java Style Guide conventions
27
+
28
+ used_by:
29
+ - lang-java-expert
@@ -0,0 +1,35 @@
1
+ # Google Java Style Guide Notes
2
+
3
+ > Source: https://google.github.io/styleguide/javaguide.html
4
+
5
+ ## Core Conventions
6
+
7
+ - Indent with 2 spaces, never tabs
8
+ - Use `UpperCamelCase` for classes, `lowerCamelCase` for methods and fields
9
+ - Constants use `UPPER_SNAKE_CASE`
10
+ - Prefer one top-level type per file
11
+ - Keep line length readable and wrap before expressions become visually dense
12
+
13
+ ## Imports
14
+
15
+ - No wildcard imports
16
+ - Static imports come before non-static imports
17
+ - Keep imports sorted ASCII-style within each block
18
+
19
+ ## Braces
20
+
21
+ - Opening brace stays on the same line
22
+ - Always use braces for `if`, `for`, `while`, and `do`
23
+
24
+ ## Classes And Members
25
+
26
+ - Order members consistently: constants, fields, constructors, public methods, private helpers
27
+ - Prefer immutable fields where possible
28
+ - Document non-obvious public API behavior with Javadoc
29
+
30
+ ## Naming
31
+
32
+ - Packages: lowercase, no underscores
33
+ - Types: nouns or noun phrases
34
+ - Methods: verbs or verb phrases
35
+ - Test methods: describe behavior, not implementation detail
@@ -0,0 +1,71 @@
1
+ # Modern Java Features
2
+
3
+ > Sources: https://openjdk.org/jeps/ (JEP 431, 440, 441, 444)
4
+
5
+ ## Virtual Threads (JEP 444)
6
+
7
+ Virtual Threads are lightweight threads managed by the JVM, enabling millions of concurrent tasks without thread pool tuning.
8
+
9
+ ### Key Properties
10
+
11
+ | Property | Platform Thread | Virtual Thread |
12
+ |----------|----------------|----------------|
13
+ | Creation cost | High (OS thread) | Low (JVM-managed) |
14
+ | Memory footprint | ~1MB per thread | ~few KB |
15
+ | Blocking behavior | Blocks OS thread | Unmounts carrier thread |
16
+ | Pooling | Needed | Not recommended |
17
+
18
+ ### Usage
19
+
20
+ ```java
21
+ try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
22
+ List<Future<String>> futures = IntStream.range(0, 10_000)
23
+ .mapToObj(i -> executor.submit(() -> fetchData(i)))
24
+ .toList();
25
+ }
26
+
27
+ Thread.ofVirtual().name("vt-worker").start(() -> processRequest());
28
+ ```
29
+
30
+ ## Pattern Matching
31
+
32
+ ```java
33
+ if (obj instanceof String s && !s.isEmpty()) {
34
+ return s.toUpperCase();
35
+ }
36
+
37
+ String format = switch (obj) {
38
+ case Integer i -> "int %d".formatted(i);
39
+ case String s -> "string %s".formatted(s);
40
+ case null -> "null";
41
+ default -> obj.toString();
42
+ };
43
+ ```
44
+
45
+ ## Records
46
+
47
+ ```java
48
+ record Point(int x, int y) {}
49
+
50
+ record Range(int min, int max) {
51
+ Range {
52
+ if (min > max) {
53
+ throw new IllegalArgumentException("min > max");
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Sealed Types
60
+
61
+ ```java
62
+ sealed interface Shape permits Circle, Rectangle {}
63
+
64
+ record Circle(double radius) implements Shape {}
65
+ record Rectangle(double width, double height) implements Shape {}
66
+ ```
67
+
68
+ ## Sequenced Collections
69
+
70
+ - Prefer `getFirst()` / `getLast()` over index arithmetic for ordered collections
71
+ - Use `reversed()` when reverse traversal is the intent
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.2.0",
3
- "lastUpdated": "2026-04-19T08:50:00.000Z",
2
+ "version": "0.3.0",
3
+ "lastUpdated": "2026-04-20T06:05:00.000Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "rules",
@@ -16,15 +16,15 @@
16
16
  },
17
17
  {
18
18
  "name": "skills",
19
- "path": ".codex/skills",
20
- "description": "Reusable skill modules (includes slash commands)",
19
+ "path": ".agents/skills",
20
+ "description": "Reusable skill modules (project-scoped repo skills)",
21
21
  "files": 112
22
22
  },
23
23
  {
24
24
  "name": "guides",
25
25
  "path": "guides",
26
26
  "description": "Reference documentation",
27
- "files": 39
27
+ "files": 40
28
28
  },
29
29
  {
30
30
  "name": "hooks",