diff --git a/src/npx-cli/commands/runtime.ts b/src/npx-cli/commands/runtime.ts index c6324a58..3e376026 100644 --- a/src/npx-cli/commands/runtime.ts +++ b/src/npx-cli/commands/runtime.ts @@ -102,7 +102,7 @@ export function runStatusCommand(): void { } /** - * Search the worker API at `GET /api/search?q=`. + * Search the worker API at `GET /api/search?query=`. */ export async function runSearchCommand(queryParts: string[]): Promise { ensureInstalledOrExit(); @@ -114,7 +114,7 @@ export async function runSearchCommand(queryParts: string[]): Promise { } const workerPort = process.env.CLAUDE_MEM_WORKER_PORT || '37777'; - const searchUrl = `http://127.0.0.1:${workerPort}/api/search?q=${encodeURIComponent(query)}`; + const searchUrl = `http://127.0.0.1:${workerPort}/api/search?query=${encodeURIComponent(query)}`; try { const response = await fetch(searchUrl); diff --git a/src/services/integrations/CodexCliInstaller.ts b/src/services/integrations/CodexCliInstaller.ts index 9645b2bc..e68168bd 100644 --- a/src/services/integrations/CodexCliInstaller.ts +++ b/src/services/integrations/CodexCliInstaller.ts @@ -6,7 +6,7 @@ * * 1. Writes/merges transcript-watch config to ~/.claude-mem/transcript-watch.json * 2. Sets up watch for ~/.codex/sessions/**\/*.jsonl using existing watcher - * 3. Injects context via ~/.codex/AGENTS.md (Codex reads this natively) + * 3. Injects context via workspace-local AGENTS.md files (Codex reads these natively) * * Anti-patterns: * - Does NOT add notify hooks -- transcript watching is sufficient @@ -67,7 +67,7 @@ function loadExistingTranscriptWatchConfig(): TranscriptWatchConfig { return parsed; } catch (parseError) { - logger.error('CODEX', 'Corrupt transcript-watch.json, creating backup', { path: configPath }, parseError as Error); + logger.error('SYSTEM', 'Corrupt transcript-watch.json, creating backup', { path: configPath }, parseError as Error); // Back up corrupt file const backupPath = `${configPath}.backup.${Date.now()}`; @@ -130,42 +130,10 @@ function writeTranscriptWatchConfig(config: TranscriptWatchConfig): void { // --------------------------------------------------------------------------- /** - * Inject claude-mem context section into ~/.codex/AGENTS.md. - * Uses the same tag pattern as CLAUDE.md and GEMINI.md. + * Remove legacy claude-mem context from ~/.codex/AGENTS.md. + * Codex now uses workspace-local AGENTS.md files to avoid cross-project bleed. * Preserves any existing user content outside the tags. */ -function injectCodexAgentsMdContext(): void { - try { - mkdirSync(CODEX_DIR, { recursive: true }); - - let existingContent = ''; - if (existsSync(CODEX_AGENTS_MD_PATH)) { - existingContent = readFileSync(CODEX_AGENTS_MD_PATH, 'utf-8'); - } - - // Initial placeholder content -- will be populated after first session - const contextContent = [ - '# Recent Activity', - '', - '', - '', - '*No context yet. Complete your first session and context will appear here.*', - ].join('\n'); - - const finalContent = replaceTaggedContent(existingContent, contextContent); - writeFileSync(CODEX_AGENTS_MD_PATH, finalContent); - console.log(` Injected context placeholder into ${CODEX_AGENTS_MD_PATH}`); - } catch (error) { - // Non-fatal -- transcript watching still works without context injection - logger.warn('CODEX', 'Failed to inject AGENTS.md context', { error: (error as Error).message }); - console.warn(` Warning: Could not inject context into AGENTS.md: ${(error as Error).message}`); - } -} - -/** - * Remove claude-mem context section from AGENTS.md. - * Preserves user content outside the tags. - */ function removeCodexAgentsMdContext(): void { try { if (!existsSync(CODEX_AGENTS_MD_PATH)) return; @@ -179,7 +147,6 @@ function removeCodexAgentsMdContext(): void { if (startIdx === -1 || endIdx === -1) return; - // Remove the tagged section and any surrounding blank lines const before = content.substring(0, startIdx).replace(/\n+$/, ''); const after = content.substring(endIdx + endTag.length).replace(/^\n+/, ''); const finalContent = (before + (after ? '\n\n' + after : '')).trim(); @@ -187,17 +154,21 @@ function removeCodexAgentsMdContext(): void { if (finalContent) { writeFileSync(CODEX_AGENTS_MD_PATH, finalContent + '\n'); } else { - // File would be empty -- leave it empty rather than deleting - // (user may have other tooling that expects it to exist) writeFileSync(CODEX_AGENTS_MD_PATH, ''); } - console.log(` Removed context section from ${CODEX_AGENTS_MD_PATH}`); + console.log(` Removed legacy global context from ${CODEX_AGENTS_MD_PATH}`); } catch (error) { - logger.warn('CODEX', 'Failed to clean AGENTS.md context', { error: (error as Error).message }); + logger.warn('SYSTEM', 'Failed to clean AGENTS.md context', { error: (error as Error).message }); } } +/** + * @deprecated Codex now uses workspace-local AGENTS.md via transcript processor fallback. + * Preserves user content outside the tags. + */ +const cleanupLegacyCodexAgentsMdContext = removeCodexAgentsMdContext; + // --------------------------------------------------------------------------- // Public API: Install // --------------------------------------------------------------------------- @@ -206,7 +177,7 @@ function removeCodexAgentsMdContext(): void { * Install Codex CLI integration for claude-mem. * * 1. Merges Codex transcript-watch config into ~/.claude-mem/transcript-watch.json - * 2. Injects context placeholder into ~/.codex/AGENTS.md + * 2. Cleans up any legacy global context block in ~/.codex/AGENTS.md * * @returns 0 on success, 1 on failure */ @@ -222,19 +193,19 @@ export async function installCodexCli(): Promise { console.log(` Watch path: ~/.codex/sessions/**/*.jsonl`); console.log(` Schema: codex (v${SAMPLE_CONFIG.schemas?.codex?.version ?? '?'})`); - // Step 2: Inject context into AGENTS.md - injectCodexAgentsMdContext(); + // Step 2: Clean up legacy global AGENTS.md context + cleanupLegacyCodexAgentsMdContext(); console.log(` Installation complete! Transcript watch config: ${DEFAULT_CONFIG_PATH} -Context file: ${CODEX_AGENTS_MD_PATH} +Context files: /AGENTS.md How it works: - claude-mem watches Codex session JSONL files for new activity - No hooks needed -- transcript watching is fully automatic - - Context from past sessions is injected via ${CODEX_AGENTS_MD_PATH} + - Context from past sessions is injected via AGENTS.md in the active Codex workspace Next steps: 1. Start claude-mem worker: npx claude-mem start @@ -284,8 +255,8 @@ export function uninstallCodexCli(): number { console.log(' No transcript-watch.json found -- nothing to remove.'); } - // Step 2: Remove context section from AGENTS.md - removeCodexAgentsMdContext(); + // Step 2: Remove legacy global context section from AGENTS.md + cleanupLegacyCodexAgentsMdContext(); console.log('\nUninstallation complete!'); console.log('Restart claude-mem worker to apply changes.\n'); @@ -340,20 +311,20 @@ export function checkCodexCliStatus(): number { // Check context config if (codexWatch.context) { console.log(` Context mode: ${codexWatch.context.mode}`); - console.log(` Context path: ${codexWatch.context.path ?? 'default'}`); + console.log(` Context path: ${codexWatch.context.path ?? '/AGENTS.md (default)'}`); console.log(` Context updates on: ${codexWatch.context.updateOn?.join(', ') ?? 'none'}`); } - // Check AGENTS.md + // Check legacy global AGENTS.md usage if (existsSync(CODEX_AGENTS_MD_PATH)) { const mdContent = readFileSync(CODEX_AGENTS_MD_PATH, 'utf-8'); if (mdContent.includes('')) { - console.log(` Context: Active (${CODEX_AGENTS_MD_PATH})`); + console.log(` Legacy global context: Present (${CODEX_AGENTS_MD_PATH})`); } else { - console.log(` Context: AGENTS.md exists but no context tags`); + console.log(` Legacy global context: Not active`); } } else { - console.log(` Context: No AGENTS.md file`); + console.log(` Legacy global context: None`); } // Check if ~/.codex/sessions exists (indicates Codex has been used) diff --git a/src/services/transcripts/config.ts b/src/services/transcripts/config.ts index 78c8e82d..1d52a00b 100644 --- a/src/services/transcripts/config.ts +++ b/src/services/transcripts/config.ts @@ -97,7 +97,6 @@ export const SAMPLE_CONFIG: TranscriptWatchConfig = { startAtEnd: true, context: { mode: 'agents', - path: '~/.codex/AGENTS.md', updateOn: ['session_start', 'session_end'] } } diff --git a/tests/codex-workspace-context.test.ts b/tests/codex-workspace-context.test.ts new file mode 100644 index 00000000..f9d01d59 --- /dev/null +++ b/tests/codex-workspace-context.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'bun:test'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +const configSource = readFileSync( + join(__dirname, '..', 'src', 'services', 'transcripts', 'config.ts'), + 'utf-8', +); +const installerSource = readFileSync( + join(__dirname, '..', 'src', 'services', 'integrations', 'CodexCliInstaller.ts'), + 'utf-8', +); + +describe('Codex workspace-local context', () => { + it('does not hardcode ~/.codex/AGENTS.md in the sample transcript watch config', () => { + expect(configSource).not.toContain("path: '~/.codex/AGENTS.md'"); + }); + + it('documents workspace-local AGENTS.md injection for Codex', () => { + expect(installerSource).toContain('workspace-local AGENTS.md'); + expect(installerSource).toContain('Context files: /AGENTS.md'); + }); + + it('cleans legacy global Codex context during install', () => { + expect(installerSource).toContain('cleanupLegacyCodexAgentsMdContext();'); + expect(installerSource).toContain('Removed legacy global context'); + }); +}); diff --git a/tests/npx-search-query-param.test.ts b/tests/npx-search-query-param.test.ts new file mode 100644 index 00000000..9f158560 --- /dev/null +++ b/tests/npx-search-query-param.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'bun:test'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +const runtimeSourcePath = join( + __dirname, + '..', + 'src', + 'npx-cli', + 'commands', + 'runtime.ts', +); +const runtimeSource = readFileSync(runtimeSourcePath, 'utf-8'); + +describe('NPX search query param', () => { + it('documents the search endpoint with query param', () => { + expect(runtimeSource).toContain('GET /api/search?query='); + }); + + it('uses query param instead of q param for worker search requests', () => { + expect(runtimeSource).toContain('/api/search?query=${encodeURIComponent(query)}'); + expect(runtimeSource).not.toContain('/api/search?q=${encodeURIComponent(query)}'); + }); +});