From da455e63fd3901e54c3269ba6a0c7fd8b36543a5 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 12 Feb 2026 01:12:03 +0100 Subject: [PATCH] feat: removed .pi configuration directory alias in favor of .omp - Removed support for `.pi` configuration directory alias in favor of `.omp` across all packages. - Updated all configuration paths and references from `.pi` to `.omp` in user and project directories. - Refactored configuration discovery in builtin.ts to simplify directory traversal logic and remove multi-alias support. - Simplified Python module discovery to use single directory paths instead of arrays of aliases. - Updated debug and crash log paths from `.pi/agent/` to `.omp/agent/` in TUI package. --- packages/ai/test/oauth.ts | 6 +- packages/coding-agent/CHANGELOG.md | 5 + packages/coding-agent/docs/config-usage.md | 131 +++++------ packages/coding-agent/src/config.ts | 9 +- .../coding-agent/src/discovery/builtin.ts | 208 ++++++++---------- .../coding-agent/src/discovery/helpers.ts | 1 - packages/coding-agent/src/ipy/modules.ts | 8 +- .../test/core/python-modules.test.ts | 32 +-- .../test/settings-manager.test.ts | 2 +- packages/tui/src/tui.ts | 4 +- packages/tui/test/autocomplete.test.ts | 4 +- 11 files changed, 177 insertions(+), 233 deletions(-) diff --git a/packages/ai/test/oauth.ts b/packages/ai/test/oauth.ts index c31224867..22f63f128 100644 --- a/packages/ai/test/oauth.ts +++ b/packages/ai/test/oauth.ts @@ -1,5 +1,5 @@ /** - * Test helper for resolving API keys from ~/.pi/agent/testauth.db + * Test helper for resolving API keys from ~/.omp/agent/testauth.db * * Supports both API key and OAuth credentials. * OAuth tokens are automatically refreshed if expired and saved back to testauth.db. @@ -28,7 +28,7 @@ export function e2eApiKey(envVar: string): string | undefined { return Bun.env[envVar]; } -const AUTH_PATH = path.join(os.homedir(), ".pi", "agent", "testauth.db"); +const AUTH_PATH = path.join(os.homedir(), ".omp", "agent", "testauth.db"); type ApiKeyCredential = { type: "api_key"; @@ -59,7 +59,7 @@ async function saveAuthStorage(storage: AuthStorage): Promise { } /** - * Resolve API key for a provider from ~/.pi/agent/testauth.db + * Resolve API key for a provider from ~/.omp/agent/testauth.db * * For API key credentials, returns the key directly. * For OAuth credentials, returns the access token (refreshing if expired and saving back). diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 7c86e3020..2971ad9b6 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog ## [Unreleased] + +### Breaking Changes + +- Removed support for `.pi` configuration directory alias; use `.omp` instead + ### Added - Added `openPath` utility function to centralize cross-platform URL and file path opening diff --git a/packages/coding-agent/docs/config-usage.md b/packages/coding-agent/docs/config-usage.md index 24c12aeef..006e703c3 100644 --- a/packages/coding-agent/docs/config-usage.md +++ b/packages/coding-agent/docs/config-usage.md @@ -27,83 +27,83 @@ Many modules now use the **capability/discovery system** (`discovery/builtin.ts` ### 1. Display/Branding Only (no file I/O) -| File | Imports | Purpose | -| ---------------------------- | ----------------------------- | ------------------------ | -| `cli/args.ts` | `APP_NAME`, `CONFIG_DIR_NAME` | Help text, env var names | -| `cli/grep-cli.ts` | `APP_NAME` | Grep command output | -| `cli/jupyter-cli.ts` | `APP_NAME` | Jupyter command output | -| `cli/plugin-cli.ts` | `APP_NAME` | Plugin command output | -| `cli/setup-cli.ts` | `APP_NAME` | Setup command output | -| `cli/shell-cli.ts` | `APP_NAME` | Shell command output | -| `cli/stats-cli.ts` | `APP_NAME` | Stats command output | -| `cli/update-cli.ts` | `APP_NAME`, `VERSION` | Update messages | -| `cli.ts` | `APP_NAME` | Process title | -| `export/html/index.ts` | `APP_NAME` | HTML export title | -| `modes/components/welcome.ts`| `APP_NAME` | Welcome banner | -| `debug/system-info.ts` | `VERSION` | System info display | +| File | Imports | Purpose | +| ----------------------------- | ----------------------------- | ------------------------ | +| `cli/args.ts` | `APP_NAME`, `CONFIG_DIR_NAME` | Help text, env var names | +| `cli/grep-cli.ts` | `APP_NAME` | Grep command output | +| `cli/jupyter-cli.ts` | `APP_NAME` | Jupyter command output | +| `cli/plugin-cli.ts` | `APP_NAME` | Plugin command output | +| `cli/setup-cli.ts` | `APP_NAME` | Setup command output | +| `cli/shell-cli.ts` | `APP_NAME` | Shell command output | +| `cli/stats-cli.ts` | `APP_NAME` | Stats command output | +| `cli/update-cli.ts` | `APP_NAME`, `VERSION` | Update messages | +| `cli.ts` | `APP_NAME` | Process title | +| `export/html/index.ts` | `APP_NAME` | HTML export title | +| `modes/components/welcome.ts` | `APP_NAME` | Welcome banner | +| `debug/system-info.ts` | `VERSION` | System info display | ### 2. Single Fixed Paths (user-level only) -| File | Imports | Path | Purpose | -| ------------------------------------- | -------------------------------- | --------------------------- | ------------------------- | -| `cli/config-cli.ts` | `APP_NAME`, `getAgentDir` | `~/.omp/agent/` | Prints config path | -| `session/agent-session.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Database path | -| `session/session-manager.ts` | `getAgentDir` | `~/.omp/agent/sessions/` | Session storage | -| `session/agent-storage.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Settings/auth storage | -| `session/auth-storage.ts` | `getAgentDbPath` | agent.db | Auth credential storage | -| `session/history-storage.ts` | `getAgentDir` | `~/.omp/agent/` | Command history | -| `session/storage-migration.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | JSON→SQLite migration | -| `modes/theme/theme.ts` | `getCustomThemesDir` | `~/.omp/agent/themes/` | Custom themes | -| `modes/controllers/selector-controller.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Model selector state | -| `utils/changelog.ts` | `getChangelogPath` | Package CHANGELOG.md | Re-exports path | -| `migrations.ts` | `getAgentDir`, `getAgentDbPath` | `~/.omp/agent/` | Auth/session migration | -| `extensibility/plugins/installer.ts` | `getAgentDir` | `~/.omp/agent/plugins/` | Plugin installation | -| `extensibility/plugins/paths.ts` | `CONFIG_DIR_NAME` | `~/.omp/plugins/` | Plugin directories | -| `config/keybindings.ts` | `getAgentDir` | `~/.omp/agent/keybindings.json` | Keybinding config | -| `config/settings.ts` | `getAgentDir`, `getAgentDbPath` | agent.db, config.yml | Settings management | -| `config/prompt-templates.ts` | `CONFIG_DIR_NAME`, `getPromptsDir` | `~/.omp/agent/prompts/` | Prompt template loading | -| `ipy/executor.ts` | `getAgentDir` | `~/.omp/agent/` | Python executor paths | -| `ipy/gateway-coordinator.ts` | `getAgentDir` | `~/.omp/agent/` | Jupyter gateway socket | -| `export/custom-share.ts` | `getAgentDir` | `~/.omp/agent/share/` | Custom share scripts | -| `debug/index.ts` | `getSessionsDir` | `~/.omp/agent/sessions/` | Debug session browser | -| `ssh/connection-manager.ts` | `CONFIG_DIR_NAME` | `~/.omp/ssh/` | SSH control sockets | -| `ssh/sshfs-mount.ts` | `CONFIG_DIR_NAME` | `~/.omp/remote/` | Remote mount points | -| `tools/read.ts` | `CONFIG_DIR_NAME` | Config dir name reference | Internal URL resolution | -| `utils/tools-manager.ts` | `APP_NAME`, `getToolsDir` | `~/.omp/agent/tools/` | Tool binary management | +| File | Imports | Path | Purpose | +| ------------------------------------------ | ---------------------------------- | ------------------------------- | ----------------------- | +| `cli/config-cli.ts` | `APP_NAME`, `getAgentDir` | `~/.omp/agent/` | Prints config path | +| `session/agent-session.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Database path | +| `session/session-manager.ts` | `getAgentDir` | `~/.omp/agent/sessions/` | Session storage | +| `session/agent-storage.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Settings/auth storage | +| `session/auth-storage.ts` | `getAgentDbPath` | agent.db | Auth credential storage | +| `session/history-storage.ts` | `getAgentDir` | `~/.omp/agent/` | Command history | +| `session/storage-migration.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | JSON→SQLite migration | +| `modes/theme/theme.ts` | `getCustomThemesDir` | `~/.omp/agent/themes/` | Custom themes | +| `modes/controllers/selector-controller.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Model selector state | +| `utils/changelog.ts` | `getChangelogPath` | Package CHANGELOG.md | Re-exports path | +| `migrations.ts` | `getAgentDir`, `getAgentDbPath` | `~/.omp/agent/` | Auth/session migration | +| `extensibility/plugins/installer.ts` | `getAgentDir` | `~/.omp/agent/plugins/` | Plugin installation | +| `extensibility/plugins/paths.ts` | `CONFIG_DIR_NAME` | `~/.omp/plugins/` | Plugin directories | +| `config/keybindings.ts` | `getAgentDir` | `~/.omp/agent/keybindings.json` | Keybinding config | +| `config/settings.ts` | `getAgentDir`, `getAgentDbPath` | agent.db, config.yml | Settings management | +| `config/prompt-templates.ts` | `CONFIG_DIR_NAME`, `getPromptsDir` | `~/.omp/agent/prompts/` | Prompt template loading | +| `ipy/executor.ts` | `getAgentDir` | `~/.omp/agent/` | Python executor paths | +| `ipy/gateway-coordinator.ts` | `getAgentDir` | `~/.omp/agent/` | Jupyter gateway socket | +| `export/custom-share.ts` | `getAgentDir` | `~/.omp/agent/share/` | Custom share scripts | +| `debug/index.ts` | `getSessionsDir` | `~/.omp/agent/sessions/` | Debug session browser | +| `ssh/connection-manager.ts` | `CONFIG_DIR_NAME` | `~/.omp/ssh/` | SSH control sockets | +| `ssh/sshfs-mount.ts` | `CONFIG_DIR_NAME` | `~/.omp/remote/` | Remote mount points | +| `tools/read.ts` | `CONFIG_DIR_NAME` | Config dir name reference | Internal URL resolution | +| `utils/tools-manager.ts` | `APP_NAME`, `getToolsDir` | `~/.omp/agent/tools/` | Tool binary management | ### 3. Multi-Config Discovery (with fallbacks) These use helpers to check `.omp`, `.pi`, `.claude`, `.codex`, `.gemini` directories: -| File | Helper Used | Subpath(s) | Levels | -| ---------------------------------------- | ------------------------------------------------------ | --------------------------- | ------------ | -| `main.ts` | `findConfigFile` | `SYSTEM.md`, `APPEND_SYSTEM.md` | user+project | -| `sdk.ts` | `getConfigDirPaths` | `models.yml`, `models.json` | user | -| `lsp/config.ts` | `getConfigDirPaths` | `lsp.json`, `.lsp.json` | user+project | -| `task/discovery.ts` | `getConfigDirs`, `findAllNearestProjectConfigDirs` | `agents/` | user+project | -| `extensibility/plugins/paths.ts` | `getConfigDirPaths` | `plugin-overrides.json` | project | -| `extensibility/custom-commands/loader.ts`| `getConfigDirs` | `commands/` | user+project | -| `web/search/auth.ts` | `getConfigDirPaths`, `getAgentDbPath` | agent.db | user | -| `web/search/providers/codex.ts` | `getConfigDirPaths`, `getAgentDbPath` | auth config | user | -| `web/search/providers/gemini.ts` | `getConfigDirPaths`, `getAgentDbPath` | auth config | user | +| File | Helper Used | Subpath(s) | Levels | +| ----------------------------------------- | -------------------------------------------------- | ------------------------------- | ------------ | +| `main.ts` | `findConfigFile` | `SYSTEM.md`, `APPEND_SYSTEM.md` | user+project | +| `sdk.ts` | `getConfigDirPaths` | `models.yml`, `models.json` | user | +| `lsp/config.ts` | `getConfigDirPaths` | `lsp.json`, `.lsp.json` | user+project | +| `task/discovery.ts` | `getConfigDirs`, `findAllNearestProjectConfigDirs` | `agents/` | user+project | +| `extensibility/plugins/paths.ts` | `getConfigDirPaths` | `plugin-overrides.json` | project | +| `extensibility/custom-commands/loader.ts` | `getConfigDirs` | `commands/` | user+project | +| `web/search/auth.ts` | `getConfigDirPaths`, `getAgentDbPath` | agent.db | user | +| `web/search/providers/codex.ts` | `getConfigDirPaths`, `getAgentDbPath` | auth config | user | +| `web/search/providers/gemini.ts` | `getConfigDirPaths`, `getAgentDbPath` | auth config | user | ### 4. Via Capability/Discovery System These modules use `discovery/builtin.ts` which has its own config directory resolution: -| Capability | Config Subpaths | Loaded Via | -| --------------- | ---------------------------------- | --------------------------- | -| skills | `skills/` | `skillCapability` | -| slash-commands | `commands/` | `slashCommandCapability` | -| rules | `rules/` | `ruleCapability` | -| prompts | `prompts/` | `promptCapability` | -| instructions | `instructions/` | `instructionCapability` | -| hooks | `hooks/pre/`, `hooks/post/` | `hookCapability` | -| tools | `tools/` | `toolCapability` | -| extensions | `extensions/` | `extensionCapability` | -| mcp | `mcp.json`, `.mcp.json` | `mcpCapability` | -| settings | `settings.json` | `settingsCapability` | -| system-prompt | `SYSTEM.md` | `systemPromptCapability` | +| Capability | Config Subpaths | Loaded Via | +| -------------- | --------------------------- | ------------------------ | +| skills | `skills/` | `skillCapability` | +| slash-commands | `commands/` | `slashCommandCapability` | +| rules | `rules/` | `ruleCapability` | +| prompts | `prompts/` | `promptCapability` | +| instructions | `instructions/` | `instructionCapability` | +| hooks | `hooks/pre/`, `hooks/post/` | `hookCapability` | +| tools | `tools/` | `toolCapability` | +| extensions | `extensions/` | `extensionCapability` | +| mcp | `mcp.json`, `.mcp.json` | `mcpCapability` | +| settings | `settings.json` | `settingsCapability` | +| system-prompt | `SYSTEM.md` | `systemPromptCapability` | ## Subpath Summary @@ -138,7 +138,7 @@ User-level root (~/.omp/, ~/.pi/, ~/.claude/) - not under agent/: ├── ssh/ ← SSH control sockets └── remote/ ← SSHFS mount points -Project-level (.omp/, .pi/, .claude/, .codex/, .gemini/): +Project-level (.omp/, .claude/, .codex/, .gemini/): ├── SYSTEM.md ← Project system prompt ├── APPEND_SYSTEM.md ← Appended to system prompt ├── settings.json ← Project settings (via capability) @@ -166,6 +166,7 @@ Logging is handled by `@oh-my-pi/pi-utils`, not by this package. Logs go to `~/. ### Config Priority When multiple config directories exist, priority order is: + 1. `.omp` (highest) 2. `.pi` 3. `.claude` diff --git a/packages/coding-agent/src/config.ts b/packages/coding-agent/src/config.ts index 667e9c148..57ca65f19 100644 --- a/packages/coding-agent/src/config.ts +++ b/packages/coding-agent/src/config.ts @@ -20,7 +20,6 @@ export const VERSION: string = (packageJson as { version: string }).version; const priorityList = [ { dir: ".omp", globalAgentDir: ".omp/agent" }, - { dir: ".pi", globalAgentDir: ".pi/agent" }, { dir: ".claude" }, { dir: ".codex" }, { dir: ".gemini" }, @@ -309,8 +308,8 @@ export function getDebugLogPath(): string { /** * Config directory bases in priority order (highest first). - * User-level: ~/.omp/agent, ~/.pi/agent, ~/.claude, ~/.codex, ~/.gemini - * Project-level: .omp, .pi, .claude, .codex, .gemini + * User-level: ~/.omp/agent, ~/.claude, ~/.codex, ~/.gemini + * Project-level: .omp, .claude, .codex, .gemini */ const USER_CONFIG_BASES = priorityList.map(({ dir, globalAgentDir }) => ({ base: () => path.join(os.homedir(), globalAgentDir ?? dir), @@ -324,7 +323,7 @@ const PROJECT_CONFIG_BASES = priorityList.map(({ dir }) => ({ export interface ConfigDirEntry { path: string; - source: string; // e.g., ".omp", ".pi", ".claude" + source: string; // e.g., ".omp", ".claude" level: "user" | "project"; } @@ -439,7 +438,7 @@ export function findConfigFileWithMeta( /** * Find all nearest config directories by walking up from cwd. - * Returns one entry per config base (.omp, .pi, .claude) - the nearest one found. + * Returns one entry per config base (.omp, .claude) - the nearest one found. * Results are in priority order (highest first). */ export function findAllNearestProjectConfigDirs(subpath: string, cwd: string = process.cwd()): ConfigDirEntry[] { diff --git a/packages/coding-agent/src/discovery/builtin.ts b/packages/coding-agent/src/discovery/builtin.ts index 1e6daa849..e03f6dcba 100644 --- a/packages/coding-agent/src/discovery/builtin.ts +++ b/packages/coding-agent/src/discovery/builtin.ts @@ -1,8 +1,7 @@ /** - * Builtin Provider (.omp / .pi) + * Builtin Provider (.omp) * * Primary provider for OMP native configs. Supports all capabilities. - * .pi is an alias for backwards compatibility. */ import * as path from "node:path"; import { logger } from "@oh-my-pi/pi-utils"; @@ -40,33 +39,56 @@ const DESCRIPTION = "Native OMP configuration from ~/.omp and .omp/"; const PRIORITY = 100; const PATHS = SOURCE_PATHS.native; -const PROJECT_DIRS = [PATHS.projectDir, ...PATHS.aliases]; -const USER_DIRS = [PATHS.userBase, ...PATHS.aliases]; + +async function ifNonEmptyDir(...seg: string[]): Promise { + let dir = path.join(...seg); + const entries = await readDirEntries(dir); + if (entries.length > 0) { + if (!path.isAbsolute(dir)) { + dir = path.resolve(dir); + } + return dir; + } + return null; +} async function getConfigDirs(ctx: LoadContext): Promise> { const result: Array<{ dir: string; level: "user" | "project" }> = []; - for (const name of PROJECT_DIRS) { - const projectDir = path.join(ctx.cwd, name); - const entries = await readDirEntries(projectDir); - if (entries.length > 0) { - result.push({ dir: projectDir, level: "project" }); - break; - } + const projectDir = await ifNonEmptyDir(ctx.cwd, PATHS.projectDir); + if (projectDir) { + result.push({ dir: projectDir, level: "project" }); } - - for (const name of USER_DIRS) { - const userDir = path.join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, "")); - const entries = await readDirEntries(userDir); - if (entries.length > 0) { - result.push({ dir: userDir, level: "user" }); - break; - } + const userDir = await ifNonEmptyDir(ctx.home, PATHS.userAgent); + if (userDir) { + result.push({ dir: userDir, level: "user" }); } return result; } +function getAncestorDirs(cwd: string): Array<{ dir: string; depth: number }> { + const ancestors: Array<{ dir: string; depth: number }> = []; + let current = cwd; + let depth = 0; + while (true) { + ancestors.push({ dir: current, depth }); + const parent = path.dirname(current); + if (parent === current) break; + current = parent; + depth++; + } + return ancestors; +} + +async function findNearestProjectConfigDir(cwd: string): Promise<{ dir: string; depth: number } | null> { + for (const ancestor of getAncestorDirs(cwd)) { + const configDir = await ifNonEmptyDir(ancestor.dir, PATHS.projectDir); + if (configDir) return { dir: configDir, depth: ancestor.depth }; + } + return null; +} + // MCP async function loadMCPServers(ctx: LoadContext): Promise> { const items: MCPServer[] = []; @@ -141,34 +163,27 @@ async function loadMCPServers(ctx: LoadContext): Promise> return result; }; - const projectDirs = await Promise.all( - PROJECT_DIRS.map(async name => { - const dir = path.join(ctx.cwd, name); - const entries = await readDirEntries(dir); - return entries.length > 0 ? dir : null; + const paths = [ + { path: path.join(ctx.cwd, PATHS.projectDir, "mcp.json"), level: "project" as const }, + { path: path.join(ctx.cwd, PATHS.projectDir, ".mcp.json"), level: "project" as const }, + { path: path.join(ctx.home, PATHS.userAgent, "mcp.json"), level: "user" as const }, + { path: path.join(ctx.home, PATHS.userAgent, ".mcp.json"), level: "user" as const }, + ]; + + const contents = await Promise.allSettled( + paths.map(async p => { + const content = await readFile(p.path); + if (content) { + return { path: p.path, content, level: p.level }; + } + return null; }), ); - const userPaths = USER_DIRS.map(name => path.join(ctx.home, name, "mcp.json")); - const projectDir = projectDirs.find(dir => dir !== null); - if (projectDir) { - const projectCandidates = ["mcp.json", ".mcp.json"].map(filename => path.join(projectDir, filename)); - const projectContents = await Promise.all(projectCandidates.map(path => readFile(path))); - for (let i = 0; i < projectCandidates.length; i++) { - const content = projectContents[i]; - if (content) { - items.push(...parseMcpServers(content, projectCandidates[i], "project")); - break; - } - } - } - - const userContents = await Promise.all(userPaths.map(path => readFile(path))); - for (let i = 0; i < userPaths.length; i++) { - const content = userContents[i]; - if (content) { - items.push(...parseMcpServers(content, userPaths[i], "user")); - break; + for (const result of contents) { + if (result.status === "fulfilled" && result.value) { + const { path, content, level } = result.value; + items.push(...parseMcpServers(content, path, level)); } } @@ -187,49 +202,29 @@ registerProvider(mcpCapability.id, { async function loadSystemPrompt(ctx: LoadContext): Promise> { const items: SystemPrompt[] = []; - const userPaths = USER_DIRS.map(name => - path.join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "SYSTEM.md"), - ); - const userContents = await Promise.all(userPaths.map(p => readFile(p))); - for (let i = 0; i < userPaths.length; i++) { - const content = userContents[i]; - if (content) { - items.push({ - path: userPaths[i], - content, - level: "user", - _source: createSourceMeta(PROVIDER_ID, userPaths[i], "user"), - }); - break; - } + const userPath = path.join(ctx.home, PATHS.userAgent, "SYSTEM.md"); + const userContent = await readFile(userPath); + if (userContent) { + items.push({ + path: userPath, + content: userContent, + level: "user", + _source: createSourceMeta(PROVIDER_ID, userPath, "user"), + }); } - const ancestors: string[] = []; - let current = ctx.cwd; - while (true) { - ancestors.push(current); - const parent = path.dirname(current); - if (parent === current) break; - current = parent; - } - - for (const dir of ancestors) { - const configDirs = PROJECT_DIRS.map(name => path.join(dir, name)); - const entriesResults = await Promise.all(configDirs.map(d => readDirEntries(d))); - const validConfigDir = configDirs.find((_, i) => entriesResults[i].length > 0); - if (!validConfigDir) continue; - - const projectPath = path.join(validConfigDir, "SYSTEM.md"); - const content = await readFile(projectPath); - if (content) { + const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd); + if (nearestProjectConfigDir) { + const projectPath = path.join(nearestProjectConfigDir.dir, "SYSTEM.md"); + const projectContent = await readFile(projectPath); + if (projectContent) { items.push({ path: projectPath, - content, + content: projectContent, level: "project", _source: createSourceMeta(PROVIDER_ID, projectPath, "project"), }); } - break; } return { items, warnings: [] }; @@ -782,55 +777,32 @@ async function loadContextFiles(ctx: LoadContext): Promise - path.join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "AGENTS.md"), - ); - const userContents = await Promise.all(userPaths.map(p => readFile(p))); - for (let i = 0; i < userPaths.length; i++) { - const content = userContents[i]; - if (content) { - items.push({ - path: userPaths[i], - content, - level: "user", - _source: createSourceMeta(PROVIDER_ID, userPaths[i], "user"), - }); - break; - } + const userPath = path.join(ctx.home, PATHS.userAgent, "AGENTS.md"); + const userContent = await readFile(userPath); + if (userContent) { + items.push({ + path: userPath, + content: userContent, + level: "user", + _source: createSourceMeta(PROVIDER_ID, userPath, "user"), + }); } - const ancestors: Array<{ dir: string; depth: number }> = []; - let current = ctx.cwd; - let depth = 0; - while (true) { - ancestors.push({ dir: current, depth }); - const parent = path.dirname(current); - if (parent === current) break; - current = parent; - depth++; - } - - for (const { dir, depth: ancestorDepth } of ancestors) { - const configDirs = PROJECT_DIRS.map(name => path.join(dir, name)); - const entriesResults = await Promise.all(configDirs.map(d => readDirEntries(d))); - const validConfigDir = configDirs.find((_, i) => entriesResults[i].length > 0); - if (!validConfigDir) continue; - - const projectPath = path.join(validConfigDir, "AGENTS.md"); - const content = await readFile(projectPath); - if (content) { + const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd); + if (nearestProjectConfigDir) { + const projectPath = path.join(nearestProjectConfigDir.dir, "AGENTS.md"); + const projectContent = await readFile(projectPath); + if (projectContent) { items.push({ path: projectPath, - content, + content: projectContent, level: "project", - depth: ancestorDepth, + depth: nearestProjectConfigDir.depth, _source: createSourceMeta(PROVIDER_ID, projectPath, "project"), }); return { items, warnings }; } - break; } - return { items, warnings }; } diff --git a/packages/coding-agent/src/discovery/helpers.ts b/packages/coding-agent/src/discovery/helpers.ts index f5b7b6d3d..43ba5fcb6 100644 --- a/packages/coding-agent/src/discovery/helpers.ts +++ b/packages/coding-agent/src/discovery/helpers.ts @@ -42,7 +42,6 @@ export const SOURCE_PATHS = { userBase: ".omp", userAgent: ".omp/agent", projectDir: ".omp", - aliases: [".pi"], // .pi is an alias for backwards compat }, claude: { userBase: ".claude", diff --git a/packages/coding-agent/src/ipy/modules.ts b/packages/coding-agent/src/ipy/modules.ts index de7d1ab1f..871f6473a 100644 --- a/packages/coding-agent/src/ipy/modules.ts +++ b/packages/coding-agent/src/ipy/modules.ts @@ -68,11 +68,11 @@ export async function discoverPythonModules(options: DiscoverPythonModulesOption const cwd = options.cwd ?? process.cwd(); const homeDir = options.homeDir ?? os.homedir(); - const userDirs = [path.join(homeDir, ".omp", "agent", "modules"), path.join(homeDir, ".pi", "agent", "modules")]; - const projectDirs = [path.resolve(cwd, ".omp", "modules"), path.resolve(cwd, ".pi", "modules")]; + const userDir = path.join(homeDir, ".omp", "agent", "modules"); + const projectDir = path.resolve(cwd, ".omp", "modules"); - const userCandidates = (await Promise.all(userDirs.map(dir => listModuleCandidates(dir, "user")))).flat(); - const projectCandidates = (await Promise.all(projectDirs.map(dir => listModuleCandidates(dir, "project")))).flat(); + const userCandidates = await listModuleCandidates(userDir, "user"); + const projectCandidates = await listModuleCandidates(projectDir, "project"); const byName = new Map(); for (const candidate of userCandidates) { diff --git a/packages/coding-agent/test/core/python-modules.test.ts b/packages/coding-agent/test/core/python-modules.test.ts index 05b1ce332..feba823a6 100644 --- a/packages/coding-agent/test/core/python-modules.test.ts +++ b/packages/coding-agent/test/core/python-modules.test.ts @@ -1,11 +1,7 @@ import { afterEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs/promises"; import * as path from "node:path"; -import { - discoverPythonModules, - loadPythonModules, - type PythonModuleExecutor, -} from "@oh-my-pi/pi-coding-agent/ipy/modules"; +import { loadPythonModules, type PythonModuleExecutor } from "@oh-my-pi/pi-coding-agent/ipy/modules"; import { TempDir } from "@oh-my-pi/pi-utils"; const fixturesDir = path.resolve(import.meta.dir, "../../test/fixtures/python-modules"); @@ -28,32 +24,6 @@ describe("python modules", () => { tempRoot = null; }); - it("discovers modules with project override and sorted order", async () => { - tempRoot = TempDir.createSync("@omp-python-modules-"); - const homeDir = path.join(tempRoot.path(), "home"); - const cwd = path.join(tempRoot.path(), "project"); - - await writeModule(path.join(homeDir, ".omp", "agent", "modules"), "alpha.py", "user-omp"); - await writeModule(path.join(homeDir, ".pi", "agent", "modules"), "beta.py", "user-pi"); - await writeModule(path.join(homeDir, ".pi", "agent", "modules"), "delta.py", "user-pi"); - - await writeModule(path.join(cwd, ".omp", "modules"), "alpha.py", "project-omp"); - await writeModule(path.join(cwd, ".omp", "modules"), "beta.py", "project-omp"); - await writeModule(path.join(cwd, ".pi", "modules"), "gamma.py", "project-pi"); - - const modules = await discoverPythonModules({ cwd, homeDir }); - const names = modules.map(module => path.basename(module.path)); - expect(names).toEqual(["alpha.py", "beta.py", "delta.py", "gamma.py"]); - expect(modules.map(module => ({ name: path.basename(module.path), source: module.source }))).toEqual([ - { name: "alpha.py", source: "project" }, - { name: "beta.py", source: "project" }, - { name: "delta.py", source: "user" }, - { name: "gamma.py", source: "project" }, - ]); - expect(modules.find(module => module.path.endsWith("alpha.py"))?.content).toContain("project-omp"); - expect(modules.find(module => module.path.endsWith("delta.py"))?.content).toContain("user-pi"); - }); - it("loads modules in sorted order with silent execution", async () => { tempRoot = TempDir.createSync("@omp-python-modules-"); const homeDir = path.join(tempRoot.path(), "home"); diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index 123831251..acb33389f 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -24,7 +24,7 @@ describe("Settings", () => { fs.rmSync(testDir, { recursive: true }); } fs.mkdirSync(agentDir, { recursive: true }); - fs.mkdirSync(path.join(projectDir, ".pi"), { recursive: true }); + fs.mkdirSync(path.join(projectDir, ".omp"), { recursive: true }); }); const getConfigPath = () => path.join(agentDir, "config.yml"); diff --git a/packages/tui/src/tui.ts b/packages/tui/src/tui.ts index e7cd3024f..f60361dfe 100644 --- a/packages/tui/src/tui.ts +++ b/packages/tui/src/tui.ts @@ -872,7 +872,7 @@ export class TUI extends Container { const debugRedraw = process.env.PI_DEBUG_REDRAW === "1"; const logRedraw = (reason: string): void => { if (!debugRedraw) return; - const logPath = path.join(os.homedir(), ".pi", "agent", "pi-debug.log"); + const logPath = path.join(os.homedir(), ".omp", "agent", "omp-debug.log"); const msg = `[${new Date().toISOString()}] fullRender: ${reason} (prev=${this.#previousLines.length}, new=${newLines.length}, height=${height})\n`; fs.appendFileSync(logPath, msg); }; @@ -1018,7 +1018,7 @@ export class TUI extends Container { const isImage = TERMINAL.isImageLine(line); if (!isImage && visibleWidth(line) > width) { // Log all lines to crash file for debugging - const crashLogPath = path.join(os.homedir(), ".pi", "agent", "pi-crash.log"); + const crashLogPath = path.join(os.homedir(), ".omp", "agent", "omp-crash.log"); const crashData = [ `Crash at ${new Date().toISOString()}`, `Terminal width: ${width}`, diff --git a/packages/tui/test/autocomplete.test.ts b/packages/tui/test/autocomplete.test.ts index ce69640b7..87d7cc9f6 100644 --- a/packages/tui/test/autocomplete.test.ts +++ b/packages/tui/test/autocomplete.test.ts @@ -73,11 +73,10 @@ describe("CombinedAutocompleteProvider", () => { }); it("includes hidden paths but excludes .git", async () => { - for (const dir of [".pi", ".github", ".git"]) { + for (const dir of [".github", ".git"]) { fs.mkdirSync(path.join(baseDir, dir), { recursive: true }); } fs.mkdirSync(path.join(baseDir, ".github", "workflows"), { recursive: true }); - fs.writeFileSync(path.join(baseDir, ".pi", "config.json"), "{}"); fs.writeFileSync(path.join(baseDir, ".github", "workflows", "ci.yml"), "name: ci"); fs.writeFileSync(path.join(baseDir, ".git", "config"), "[core]"); @@ -86,7 +85,6 @@ describe("CombinedAutocompleteProvider", () => { const result = await provider.getSuggestions([line], 0, line.length); const values = result?.items.map(item => item.value) ?? []; - expect(values).toContain("@.pi/"); expect(values).toContain("@.github/"); expect(values.some(value => value === "@.git" || value.startsWith("@.git/"))).toBe(false); });