ai-nexus 1.3.14 → 1.3.15

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.ko.md CHANGED
@@ -90,7 +90,7 @@ npx ai-nexus install --rules github.com/your-org/team-rules
90
90
  |------|-----------|-----------|
91
91
  | **Claude Code** | 시맨틱 라우터가 프롬프트마다 동적으로 룰 교체 | 필요한 룰만 로딩 |
92
92
  | **Cursor** | 룰을 `.mdc` 형식으로 변환; Cursor 내장 검색이 필터링 | Cursor 검색에 의존 |
93
- | **Codex** | 정적 `AGENTS.md` (동적 로딩 없음) | 전체 룰 로딩 |
93
+ | **Codex** | 집계된 `AGENTS.md` (개별 병합) | 전체 룰 로딩 |
94
94
 
95
95
  ---
96
96
 
@@ -136,9 +136,9 @@ alwaysApply: false
136
136
 
137
137
  변환 후 룰 필터링은 **Cursor 내장 시맨틱 서치**가 처리합니다 — ai-nexus가 Cursor에서 라우터를 실행하지는 않습니다. 핵심 가치는 통합 룰 관리: 룰을 한 번 작성하면 Claude Code, Cursor, Codex에서 모두 사용할 수 있습니다.
138
138
 
139
- ### Codex: 정적
139
+ ### Codex: 집계
140
140
 
141
- 세션 시작 단일 `AGENTS.md` 파일이 로드됩니다. 동적 로딩 없음.
141
+ 개별 파일들이 단일 `AGENTS.md` 파일로 자동 병합되며, 세션 시작 시 로드됩니다. 동적 로딩 없음.
142
142
 
143
143
  ---
144
144
 
package/README.md CHANGED
@@ -90,7 +90,7 @@ npx ai-nexus install --rules github.com/your-org/team-rules
90
90
  |------|--------------|----------------|
91
91
  | **Claude Code** | Semantic Router dynamically swaps rules per prompt | Only relevant rules loaded |
92
92
  | **Cursor** | Converts rules to `.mdc` format; Cursor's built-in search handles filtering | Depends on Cursor's search |
93
- | **Codex** | Static `AGENTS.md` (no dynamic loading) | All rules loaded |
93
+ | **Codex** | Aggregated `AGENTS.md` (rules merged into single file) | All rules loaded |
94
94
 
95
95
  ---
96
96
 
@@ -136,9 +136,9 @@ alwaysApply: false
136
136
 
137
137
  After conversion, **Cursor's built-in semantic search** handles rule filtering — ai-nexus does not run a router for Cursor. The value is unified rule management: write rules once, use them across Claude Code, Cursor, and Codex.
138
138
 
139
- ### Codex: Static Rules
139
+ ### Codex: Aggregated Rules
140
140
 
141
- A single `AGENTS.md` file is loaded at session start. No dynamic loading.
141
+ Individual rule files are aggregated into a single `AGENTS.md` file, which is loaded at session start. No dynamic loading.
142
142
 
143
143
  ---
144
144
 
@@ -5,7 +5,7 @@ import { createRequire } from 'module';
5
5
  import inquirer from 'inquirer';
6
6
  import chalk from 'chalk';
7
7
  import ora from 'ora';
8
- import { getTargetDir, getConfigPath, ensureDir, createSymlink, scanDir, computeFileHashes, } from '../utils/files.js';
8
+ import { getTargetDir, getConfigPath, ensureDir, createSymlink, scanDir, computeFileHashes, aggregateToAgentsMd, } from '../utils/files.js';
9
9
  import { scanConfigDir } from '../utils/config-scanner.js';
10
10
  // Convert .md to .mdc format for Cursor
11
11
  function convertToMdc(content, filename) {
@@ -376,13 +376,13 @@ async function install(selections) {
376
376
  }
377
377
  }
378
378
  }
379
- // Copy AGENTS.md for Codex
379
+ // Generate aggregated AGENTS.md for Codex
380
380
  if (tool === 'codex') {
381
- const agentsFile = path.join(builtinConfigDir, 'codex', 'AGENTS.md');
382
381
  const destAgents = path.join(toolDir, 'AGENTS.md');
383
382
  // Local priority: skip if file exists
384
- if (!fs.existsSync(destAgents) && fs.existsSync(agentsFile)) {
385
- fs.copyFileSync(agentsFile, destAgents);
383
+ if (!fs.existsSync(destAgents)) {
384
+ const content = aggregateToAgentsMd(configDir, selectedFiles);
385
+ fs.writeFileSync(destAgents, content);
386
386
  }
387
387
  }
388
388
  }
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import os from 'os';
4
4
  import chalk from 'chalk';
5
5
  import inquirer from 'inquirer';
6
- import { detectInstall, scanDir, compareConfigs, ensureDir, computeFileHashes } from '../utils/files.js';
6
+ import { detectInstall, scanDir, compareConfigs, ensureDir, computeFileHashes, aggregateToAgentsMd } from '../utils/files.js';
7
7
  import crypto from 'crypto';
8
8
  import { updateRepo } from '../utils/git.js';
9
9
  export async function update(options = {}) {
@@ -184,6 +184,32 @@ export async function update(options = {}) {
184
184
  hasChanges = true;
185
185
  }
186
186
  }
187
+ // Regenerate AGENTS.md for Codex
188
+ if (meta.tools && meta.tools.includes('codex')) {
189
+ const codexDir = path.join(targetDir, '.codex');
190
+ const destAgents = path.join(codexDir, 'AGENTS.md');
191
+ const content = aggregateToAgentsMd(configDir);
192
+ if (fs.existsSync(destAgents)) {
193
+ const existing = fs.readFileSync(destAgents, 'utf8');
194
+ if (existing !== content) {
195
+ if (options.force) {
196
+ ensureDir(codexDir);
197
+ fs.writeFileSync(destAgents, content);
198
+ console.log(chalk.green(' ✓ Codex AGENTS.md regenerated'));
199
+ hasChanges = true;
200
+ }
201
+ else {
202
+ console.log(chalk.gray(' Codex AGENTS.md has changes (use --force to regenerate)'));
203
+ }
204
+ }
205
+ }
206
+ else {
207
+ ensureDir(codexDir);
208
+ fs.writeFileSync(destAgents, content);
209
+ console.log(chalk.green(' ✓ Codex AGENTS.md generated'));
210
+ hasChanges = true;
211
+ }
212
+ }
187
213
  // Update metadata (refresh file hashes for copy mode)
188
214
  meta.updatedAt = new Date().toISOString();
189
215
  if (meta.mode === 'copy') {
@@ -15,6 +15,12 @@ export declare function getConfigPath(scope: 'project' | 'global'): string;
15
15
  export declare function ensureDir(dir: string): void;
16
16
  export declare function copyFile(src: string, dest: string): void;
17
17
  export declare function createSymlink(target: string, linkPath: string): void;
18
+ /**
19
+ * Aggregate rule files into a single AGENTS.md content string.
20
+ * If selectedFiles is provided, only those files are included.
21
+ * Otherwise, all .md files in each category directory are included.
22
+ */
23
+ export declare function aggregateToAgentsMd(configDir: string, selectedFiles?: Record<string, string[]>): string;
18
24
  export declare function detectInstall(): {
19
25
  configPath: string;
20
26
  scope: 'project' | 'global';
@@ -83,6 +83,56 @@ export function createSymlink(target, linkPath) {
83
83
  }
84
84
  fs.symlinkSync(target, linkPath);
85
85
  }
86
+ const AGENTS_CATEGORIES = ['rules', 'commands', 'skills', 'agents', 'contexts'];
87
+ const CATEGORY_LABELS = {
88
+ rules: 'Rules',
89
+ commands: 'Commands',
90
+ skills: 'Skills',
91
+ agents: 'Agents',
92
+ contexts: 'Contexts',
93
+ };
94
+ function stripFrontmatter(content) {
95
+ return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n*/, '').trim();
96
+ }
97
+ /**
98
+ * Aggregate rule files into a single AGENTS.md content string.
99
+ * If selectedFiles is provided, only those files are included.
100
+ * Otherwise, all .md files in each category directory are included.
101
+ */
102
+ export function aggregateToAgentsMd(configDir, selectedFiles) {
103
+ const sections = [];
104
+ sections.push('# AI Agent Guidelines\n');
105
+ sections.push('> Auto-generated by ai-nexus. Do not edit manually.\n');
106
+ for (const category of AGENTS_CATEGORIES) {
107
+ const catDir = path.join(configDir, category);
108
+ if (!fs.existsSync(catDir))
109
+ continue;
110
+ let files;
111
+ if (selectedFiles && selectedFiles[category]) {
112
+ files = selectedFiles[category];
113
+ }
114
+ else {
115
+ // Scan all .md files in the category directory
116
+ files = fs.readdirSync(catDir).filter(f => f.endsWith('.md'));
117
+ }
118
+ if (files.length === 0)
119
+ continue;
120
+ const label = CATEGORY_LABELS[category] || category;
121
+ sections.push(`## ${label}\n`);
122
+ for (const file of files) {
123
+ const filePath = path.join(catDir, file);
124
+ if (!fs.existsSync(filePath))
125
+ continue;
126
+ const raw = fs.readFileSync(filePath, 'utf8');
127
+ const body = stripFrontmatter(raw);
128
+ if (!body)
129
+ continue;
130
+ sections.push(body);
131
+ sections.push('');
132
+ }
133
+ }
134
+ return sections.join('\n').trim() + '\n';
135
+ }
86
136
  export function detectInstall() {
87
137
  const projectPath = path.join(process.cwd(), '.ai-nexus');
88
138
  if (fs.existsSync(projectPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-nexus",
3
- "version": "1.3.14",
3
+ "version": "1.3.15",
4
4
  "description": "Unified rule manager for Claude Code, Cursor, and Codex - write once, use everywhere, save tokens",
5
5
  "main": "dist/index.js",
6
6
  "bin": {