diff --git a/.codex/agents/explorer.toml b/.codex/agents/explorer.toml index af7c1965..31798d95 100644 --- a/.codex/agents/explorer.toml +++ b/.codex/agents/explorer.toml @@ -1,4 +1,4 @@ -model = "o4-mini" +model = "gpt-5.4" model_reasoning_effort = "medium" sandbox_mode = "read-only" diff --git a/.codex/config.toml b/.codex/config.toml index c60224ac..3a5dd147 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -10,8 +10,9 @@ # - https://developers.openai.com/codex/multi-agent # Model selection -model = "o4-mini" -model_provider = "openai" +# Leave `model` and `model_provider` unset so Codex CLI uses its current +# built-in defaults. Uncomment and pin them only if you intentionally want +# repo-local or global model overrides. # Top-level runtime settings (current Codex schema) approval_policy = "on-request" diff --git a/README.md b/README.md index 568f069b..9be5f09a 100644 --- a/README.md +++ b/README.md @@ -940,6 +940,7 @@ Codex macOS app: - Open this repository as your workspace. - The root `AGENTS.md` is auto-detected. - `.codex/config.toml` and `.codex/agents/*.toml` work best when kept project-local. +- The reference `.codex/config.toml` intentionally does not pin `model` or `model_provider`, so Codex uses its own current default unless you override it. - Optional: copy `.codex/config.toml` to `~/.codex/config.toml` for global defaults; keep the multi-agent role files project-local unless you also copy `.codex/agents/`. ### What's Included diff --git a/docs/ja-JP/skills/django-security/SKILL.md b/docs/ja-JP/skills/django-security/SKILL.md index 798824ea..55d20725 100644 --- a/docs/ja-JP/skills/django-security/SKILL.md +++ b/docs/ja-JP/skills/django-security/SKILL.md @@ -581,7 +581,7 @@ LOGGING = { | 強力なシークレット | SECRET_KEYに環境変数を使用 | | パスワード検証 | すべてのパスワードバリデータを有効化 | | CSRF保護 | デフォルトで有効、無効にしない | -| XSS防止 | Djangoは自動エスケープ、ユーザー入力で`|safe`を使用しない | +| XSS防止 | Djangoは自動エスケープ、ユーザー入力で\|safeを使用しない | | SQLインジェクション | ORMを使用、クエリで文字列を連結しない | | ファイルアップロード | ファイルタイプとサイズを検証 | | レート制限 | APIエンドポイントをスロットル | diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index 036b82ca..478d8afc 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -939,13 +939,14 @@ Codex macOS 应用: * 将此仓库作为您的工作区打开。 * 根目录的 `AGENTS.md` 会被自动检测。 +* 参考 `.codex/config.toml` 故意不固定 `model` 或 `model_provider`,因此 Codex 会使用它自己的当前默认值,除非您显式覆盖。 * 可选:将 `.codex/config.toml` 复制到 `~/.codex/config.toml` 以实现 CLI/应用行为一致性。 ### 包含内容 | 组件 | 数量 | 详情 | |-----------|-------|---------| -| 配置 | 1 | `.codex/config.toml` — 模型、权限、MCP 服务器、持久指令 | +| 配置 | 1 | `.codex/config.toml` — 权限、MCP 服务器、通知和配置文件 | | AGENTS.md | 2 | 根目录(通用)+ `.codex/AGENTS.md`(Codex 特定补充) | | 技能 | 16 | `.agents/skills/` — 每个技能包含 SKILL.md + agents/openai.yaml | | MCP 服务器 | 4 | GitHub、Context7、Memory、Sequential Thinking(基于命令) | diff --git a/docs/zh-TW/agents/database-reviewer.md b/docs/zh-TW/agents/database-reviewer.md index 7c328feb..968aa47b 100644 --- a/docs/zh-TW/agents/database-reviewer.md +++ b/docs/zh-TW/agents/database-reviewer.md @@ -129,7 +129,7 @@ CREATE INDEX orders_customer_id_idx ON orders (customer_id); | 索引類型 | 使用場景 | 運算子 | |----------|----------|--------| | **B-tree**(預設)| 等於、範圍 | `=`、`<`、`>`、`BETWEEN`、`IN` | -| **GIN** | 陣列、JSONB、全文搜尋 | `@>`、`?`、`?&`、`?|`、`@@` | +| **GIN** | 陣列、JSONB、全文搜尋 | `@>`、`?`、`?&`、?\|、`@@` | | **BRIN** | 大型時序表 | 排序資料的範圍查詢 | | **Hash** | 僅等於 | `=`(比 B-tree 略快)| diff --git a/skills/videodb/SKILL.md b/skills/videodb/SKILL.md index 1e6e4403..fe3216d7 100644 --- a/skills/videodb/SKILL.md +++ b/skills/videodb/SKILL.md @@ -108,7 +108,7 @@ The user must set `VIDEO_DB_API_KEY` using **either** method: - **Export in terminal** (before starting Claude): `export VIDEO_DB_API_KEY=your-key` - **Project `.env` file**: Save `VIDEO_DB_API_KEY=your-key` in the project's `.env` file -Get a free API key at (50 free uploads, no credit card). +Get a free API key at [console.videodb.io](https://console.videodb.io) (50 free uploads, no credit card). **Do NOT** read, write, or handle the API key yourself. Always let the user set it. diff --git a/skills/videodb/reference/capture-reference.md b/skills/videodb/reference/capture-reference.md index 7dc98f60..b002c493 100644 --- a/skills/videodb/reference/capture-reference.md +++ b/skills/videodb/reference/capture-reference.md @@ -107,7 +107,7 @@ Use [scripts/ws_listener.py](../scripts/ws_listener.py) to connect and dump even } ``` -> For latest details, see +> For latest details, see [the realtime context docs](https://docs.videodb.io/pages/ingest/capture-sdks/realtime-context.md). --- diff --git a/tests/codex-config.test.js b/tests/codex-config.test.js new file mode 100644 index 00000000..8cff7983 --- /dev/null +++ b/tests/codex-config.test.js @@ -0,0 +1,70 @@ +/** + * Tests for `.codex/config.toml` reference defaults. + * + * Run with: node tests/codex-config.test.js + */ + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +function test(name, fn) { + try { + fn(); + console.log(` ✓ ${name}`); + return true; + } catch (err) { + console.log(` ✗ ${name}`); + console.log(` Error: ${err.message}`); + return false; + } +} + +const repoRoot = path.join(__dirname, '..'); +const configPath = path.join(repoRoot, '.codex', 'config.toml'); +const config = fs.readFileSync(configPath, 'utf8'); +const codexAgentsDir = path.join(repoRoot, '.codex', 'agents'); + +let passed = 0; +let failed = 0; + +if ( + test('reference config does not pin a top-level model', () => { + assert.ok(!/^model\s*=/m.test(config), 'Expected `.codex/config.toml` to inherit the CLI default model'); + }) +) + passed++; +else failed++; + +if ( + test('reference config does not pin a top-level model provider', () => { + assert.ok( + !/^model_provider\s*=/m.test(config), + 'Expected `.codex/config.toml` to inherit the CLI default provider', + ); + }) +) + passed++; +else failed++; + +if ( + test('sample Codex role configs do not use o4-mini', () => { + const roleFiles = fs.readdirSync(codexAgentsDir).filter(file => file.endsWith('.toml')); + assert.ok(roleFiles.length > 0, 'Expected sample role config files under `.codex/agents`'); + + for (const roleFile of roleFiles) { + const rolePath = path.join(codexAgentsDir, roleFile); + const roleConfig = fs.readFileSync(rolePath, 'utf8'); + assert.ok( + !/^model\s*=\s*"o4-mini"$/m.test(roleConfig), + `Expected sample role config to avoid o4-mini: ${roleFile}`, + ); + } + }) +) + passed++; +else failed++; + +console.log(`\nPassed: ${passed}`); +console.log(`Failed: ${failed}`); +process.exit(failed > 0 ? 1 : 0); diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index 0352c415..57c19c56 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -98,6 +98,44 @@ function cleanupTestDir(testDir) { fs.rmSync(testDir, { recursive: true, force: true }); } +function normalizeComparablePath(targetPath) { + if (!targetPath) return ''; + + let normalizedPath = String(targetPath).trim().replace(/\\/g, '/'); + + if (/^\/[a-zA-Z]\//.test(normalizedPath)) { + normalizedPath = `${normalizedPath[1]}:/${normalizedPath.slice(3)}`; + } + + if (/^[a-zA-Z]:\//.test(normalizedPath)) { + normalizedPath = `${normalizedPath[0].toUpperCase()}:${normalizedPath.slice(2)}`; + } + + try { + normalizedPath = fs.realpathSync(normalizedPath); + } catch { + // Fall through to string normalization when the path cannot be resolved directly. + } + + return path.normalize(normalizedPath).replace(/\\/g, '/').replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`); +} + +function pathsReferToSameLocation(leftPath, rightPath) { + const normalizedLeftPath = normalizeComparablePath(leftPath); + const normalizedRightPath = normalizeComparablePath(rightPath); + + if (!normalizedLeftPath || !normalizedRightPath) return false; + if (normalizedLeftPath === normalizedRightPath) return true; + + try { + const leftStats = fs.statSync(normalizedLeftPath); + const rightStats = fs.statSync(normalizedRightPath); + return leftStats.dev === rightStats.dev && leftStats.ino === rightStats.ino; + } catch { + return false; + } +} + function createCommandShim(binDir, baseName, logFile) { fs.mkdirSync(binDir, { recursive: true }); @@ -155,6 +193,7 @@ async function runTests() { let passed = 0; let failed = 0; + let skipped = 0; const scriptsDir = path.join(__dirname, '..', '..', 'scripts', 'hooks'); @@ -2148,12 +2187,12 @@ async function runTests() { passed++; else failed++; - if ( + if (process.platform === 'win32') { + console.log(' - detect-project writes project metadata to the registry and project directory'); + console.log(' (skipped — bash script paths are not Windows-compatible)'); + skipped++; + } else if ( await asyncTest('detect-project writes project metadata to the registry and project directory', async () => { - if (process.platform === 'win32') { - console.log(' (skipped — bash script paths are not Windows-compatible)'); - return true; - } const testRoot = createTestDir(); const homeDir = path.join(testRoot, 'home'); const repoDir = path.join(testRoot, 'repo'); @@ -2189,9 +2228,9 @@ async function runTests() { assert.strictEqual(code, 0, `detect-project should source cleanly, stderr: ${stderr}`); - const [projectId, projectDir] = stdout.trim().split(/\r?\n/); + const [projectId] = stdout.trim().split(/\r?\n/); const registryPath = path.join(homeDir, '.claude', 'homunculus', 'projects.json'); - const projectMetadataPath = path.join(projectDir, 'project.json'); + const projectMetadataPath = path.join(homeDir, '.claude', 'homunculus', 'projects', projectId, 'project.json'); assert.ok(projectId, 'detect-project should emit a project id'); assert.ok(fs.existsSync(registryPath), 'projects.json should be created'); @@ -2203,7 +2242,13 @@ async function runTests() { assert.ok(registry[projectId], 'registry should contain the detected project'); assert.strictEqual(metadata.id, projectId, 'project.json should include the detected id'); assert.strictEqual(metadata.name, path.basename(repoDir), 'project.json should include the repo name'); - assert.strictEqual(fs.realpathSync(metadata.root), fs.realpathSync(repoDir), 'project.json should include the repo root'); + const normalizedMetadataRoot = normalizeComparablePath(metadata.root); + const normalizedRepoDir = normalizeComparablePath(repoDir); + assert.ok(normalizedMetadataRoot, 'project.json should include a non-empty repo root'); + assert.ok( + pathsReferToSameLocation(normalizedMetadataRoot, normalizedRepoDir), + `project.json should include the repo root (expected ${normalizedRepoDir}, got ${normalizedMetadataRoot})`, + ); assert.strictEqual(metadata.remote, 'https://github.com/example/ecc-test.git', 'project.json should include the sanitized remote'); assert.ok(metadata.created_at, 'project.json should include created_at'); assert.ok(metadata.last_seen, 'project.json should include last_seen'); @@ -4242,12 +4287,12 @@ async function runTests() { // ── Round 74: session-start.js main().catch handler ── console.log('\nRound 74: session-start.js (main catch — unrecoverable error):'); - if ( + if (process.platform === 'win32') { + console.log(' - session-start exits 0 with error message when HOME is non-directory'); + console.log(' (skipped — /dev/null not available on Windows)'); + skipped++; + } else if ( await asyncTest('session-start exits 0 with error message when HOME is non-directory', async () => { - if (process.platform === 'win32') { - console.log(' (skipped — /dev/null not available on Windows)'); - return; - } // HOME=/dev/null makes ensureDir(sessionsDir) throw ENOTDIR, // which propagates to main().catch — the top-level error boundary const result = await runScript(path.join(scriptsDir, 'session-start.js'), '', { @@ -4264,12 +4309,12 @@ async function runTests() { // ── Round 75: pre-compact.js main().catch handler ── console.log('\nRound 75: pre-compact.js (main catch — unrecoverable error):'); - if ( + if (process.platform === 'win32') { + console.log(' - pre-compact exits 0 with error message when HOME is non-directory'); + console.log(' (skipped — /dev/null not available on Windows)'); + skipped++; + } else if ( await asyncTest('pre-compact exits 0 with error message when HOME is non-directory', async () => { - if (process.platform === 'win32') { - console.log(' (skipped — /dev/null not available on Windows)'); - return; - } // HOME=/dev/null makes ensureDir(sessionsDir) throw ENOTDIR, // which propagates to main().catch — the top-level error boundary const result = await runScript(path.join(scriptsDir, 'pre-compact.js'), '', { @@ -4286,12 +4331,12 @@ async function runTests() { // ── Round 75: session-end.js main().catch handler ── console.log('\nRound 75: session-end.js (main catch — unrecoverable error):'); - if ( + if (process.platform === 'win32') { + console.log(' - session-end exits 0 with error message when HOME is non-directory'); + console.log(' (skipped — /dev/null not available on Windows)'); + skipped++; + } else if ( await asyncTest('session-end exits 0 with error message when HOME is non-directory', async () => { - if (process.platform === 'win32') { - console.log(' (skipped — /dev/null not available on Windows)'); - return; - } // HOME=/dev/null makes ensureDir(sessionsDir) throw ENOTDIR inside main(), // which propagates to runMain().catch — the top-level error boundary const result = await runScript(path.join(scriptsDir, 'session-end.js'), '{}', { @@ -4308,12 +4353,12 @@ async function runTests() { // ── Round 76: evaluate-session.js main().catch handler ── console.log('\nRound 76: evaluate-session.js (main catch — unrecoverable error):'); - if ( + if (process.platform === 'win32') { + console.log(' - evaluate-session exits 0 with error message when HOME is non-directory'); + console.log(' (skipped — /dev/null not available on Windows)'); + skipped++; + } else if ( await asyncTest('evaluate-session exits 0 with error message when HOME is non-directory', async () => { - if (process.platform === 'win32') { - console.log(' (skipped — /dev/null not available on Windows)'); - return; - } // HOME=/dev/null makes ensureDir(learnedSkillsPath) throw ENOTDIR, // which propagates to main().catch — the top-level error boundary const result = await runScript(path.join(scriptsDir, 'evaluate-session.js'), '{}', { @@ -4330,12 +4375,12 @@ async function runTests() { // ── Round 76: suggest-compact.js main().catch handler ── console.log('\nRound 76: suggest-compact.js (main catch — double-failure):'); - if ( + if (process.platform === 'win32') { + console.log(' - suggest-compact exits 0 with error when TMPDIR is non-directory'); + console.log(' (skipped — /dev/null not available on Windows)'); + skipped++; + } else if ( await asyncTest('suggest-compact exits 0 with error when TMPDIR is non-directory', async () => { - if (process.platform === 'win32') { - console.log(' (skipped — /dev/null not available on Windows)'); - return; - } // TMPDIR=/dev/null causes openSync to fail (ENOTDIR), then the catch // fallback writeFile also fails, propagating to main().catch const result = await runScript(path.join(scriptsDir, 'suggest-compact.js'), '', { @@ -4807,7 +4852,8 @@ Some random content without the expected ### Context to Load section console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`); console.log(`Failed: ${failed}`); - console.log(`Total: ${passed + failed}\n`); + console.log(`Skipped: ${skipped}`); + console.log(`Total: ${passed + failed + skipped}\n`); process.exit(failed > 0 ? 1 : 0); }