refactor: reduce code duplication in agent-discovery.ts

Addresses PR feedback to reduce duplicated code in scanAgentsDirectory
by introducing an FsAdapter interface that abstracts the differences
between systemPaths (user directory) and secureFs (project directory).

Changes:
- Extract parseAgentContent helper for parsing agent file content
- Add FsAdapter interface with exists, readdir, and readFile methods
- Create createSystemPathAdapter for user-level paths
- Create createSecureFsAdapter for project-level paths
- Refactor scanAgentsDirectory to use a single loop with the adapter

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2026-01-08 23:02:41 +01:00
parent 50da1b401c
commit e649c4ced5

View File

@@ -24,7 +24,7 @@ export interface FilesystemAgent {
} }
/** /**
* Parse agent .md file frontmatter and content * Parse agent content string into AgentDefinition
* Format: * Format:
* --- * ---
* name: agent-name # Optional * name: agent-name # Optional
@@ -34,15 +34,7 @@ export interface FilesystemAgent {
* --- * ---
* System prompt content here... * System prompt content here...
*/ */
async function parseAgentFile( function parseAgentContent(content: string, filePath: string): AgentDefinition | null {
filePath: string,
isSystemPath: boolean
): Promise<AgentDefinition | null> {
try {
const content = isSystemPath
? ((await systemPaths.systemPathReadFile(filePath, 'utf-8')) as string)
: ((await secureFs.readFile(filePath, 'utf-8')) as string);
// Extract frontmatter // Extract frontmatter
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!frontmatterMatch) { if (!frontmatterMatch) {
@@ -89,6 +81,81 @@ async function parseAgentFile(
tools, tools,
model, model,
}; };
}
/**
* Directory entry with type information
*/
interface DirEntry {
name: string;
isFile: boolean;
isDirectory: boolean;
}
/**
* Filesystem adapter interface for abstracting systemPaths vs secureFs
*/
interface FsAdapter {
exists: (filePath: string) => Promise<boolean>;
readdir: (dirPath: string) => Promise<DirEntry[]>;
readFile: (filePath: string) => Promise<string>;
}
/**
* Create a filesystem adapter for system paths (user directory)
*/
function createSystemPathAdapter(): FsAdapter {
return {
exists: (filePath) => Promise.resolve(systemPaths.systemPathExists(filePath)),
readdir: async (dirPath) => {
const entryNames = await systemPaths.systemPathReaddir(dirPath);
const entries: DirEntry[] = [];
for (const name of entryNames) {
const stat = await systemPaths.systemPathStat(path.join(dirPath, name));
entries.push({
name,
isFile: stat.isFile(),
isDirectory: stat.isDirectory(),
});
}
return entries;
},
readFile: (filePath) => systemPaths.systemPathReadFile(filePath, 'utf-8') as Promise<string>,
};
}
/**
* Create a filesystem adapter for project paths (secureFs)
*/
function createSecureFsAdapter(): FsAdapter {
return {
exists: (filePath) =>
secureFs
.access(filePath)
.then(() => true)
.catch(() => false),
readdir: async (dirPath) => {
const entries = await secureFs.readdir(dirPath, { withFileTypes: true });
return entries.map((entry) => ({
name: entry.name,
isFile: entry.isFile(),
isDirectory: entry.isDirectory(),
}));
},
readFile: (filePath) => secureFs.readFile(filePath, 'utf-8') as Promise<string>,
};
}
/**
* Parse agent file using the provided filesystem adapter
*/
async function parseAgentFileWithAdapter(
filePath: string,
fsAdapter: FsAdapter
): Promise<AgentDefinition | null> {
try {
const content = await fsAdapter.readFile(filePath);
return parseAgentContent(content, filePath);
} catch (error) { } catch (error) {
logger.error(`Failed to parse agent file: ${filePath}`, error); logger.error(`Failed to parse agent file: ${filePath}`, error);
return null; return null;
@@ -106,72 +173,25 @@ async function scanAgentsDirectory(
source: 'user' | 'project' source: 'user' | 'project'
): Promise<FilesystemAgent[]> { ): Promise<FilesystemAgent[]> {
const agents: FilesystemAgent[] = []; const agents: FilesystemAgent[] = [];
const isSystemPath = source === 'user'; // User directories use systemPaths const fsAdapter = source === 'user' ? createSystemPathAdapter() : createSecureFsAdapter();
try { try {
// Check if directory exists // Check if directory exists
const exists = isSystemPath const exists = await fsAdapter.exists(baseDir);
? await systemPaths.systemPathExists(baseDir)
: await secureFs
.access(baseDir)
.then(() => true)
.catch(() => false);
if (!exists) { if (!exists) {
logger.debug(`Directory does not exist: ${baseDir}`); logger.debug(`Directory does not exist: ${baseDir}`);
return agents; return agents;
} }
// Read all entries in the directory // Read all entries in the directory
if (isSystemPath) { const entries = await fsAdapter.readdir(baseDir);
// For system paths (user directory)
const entryNames = await systemPaths.systemPathReaddir(baseDir);
for (const entryName of entryNames) {
const entryPath = path.join(baseDir, entryName);
const stat = await systemPaths.systemPathStat(entryPath);
// Check for flat .md file format (agent-name.md)
if (stat.isFile() && entryName.endsWith('.md')) {
const agentName = entryName.slice(0, -3); // Remove .md extension
const definition = await parseAgentFile(entryPath, true);
if (definition) {
agents.push({
name: agentName,
definition,
source,
filePath: entryPath,
});
logger.debug(`Discovered ${source} agent (flat): ${agentName}`);
}
}
// Check for subdirectory format (agent-name/AGENT.md)
else if (stat.isDirectory()) {
const agentFilePath = path.join(entryPath, 'AGENT.md');
const agentFileExists = await systemPaths.systemPathExists(agentFilePath);
if (agentFileExists) {
const definition = await parseAgentFile(agentFilePath, true);
if (definition) {
agents.push({
name: entryName,
definition,
source,
filePath: agentFilePath,
});
logger.debug(`Discovered ${source} agent (subdirectory): ${entryName}`);
}
}
}
}
} else {
// For project paths (use secureFs)
const entries = await secureFs.readdir(baseDir, { withFileTypes: true });
for (const entry of entries) { for (const entry of entries) {
// Check for flat .md file format (agent-name.md) // Check for flat .md file format (agent-name.md)
if (entry.isFile() && entry.name.endsWith('.md')) { if (entry.isFile && entry.name.endsWith('.md')) {
const agentName = entry.name.slice(0, -3); // Remove .md extension const agentName = entry.name.slice(0, -3); // Remove .md extension
const agentFilePath = path.join(baseDir, entry.name); const agentFilePath = path.join(baseDir, entry.name);
const definition = await parseAgentFile(agentFilePath, false); const definition = await parseAgentFileWithAdapter(agentFilePath, fsAdapter);
if (definition) { if (definition) {
agents.push({ agents.push({
name: agentName, name: agentName,
@@ -183,17 +203,12 @@ async function scanAgentsDirectory(
} }
} }
// Check for subdirectory format (agent-name/AGENT.md) // Check for subdirectory format (agent-name/AGENT.md)
else if (entry.isDirectory()) { else if (entry.isDirectory) {
const agentDir = path.join(baseDir, entry.name); const agentFilePath = path.join(baseDir, entry.name, 'AGENT.md');
const agentFilePath = path.join(agentDir, 'AGENT.md'); const agentFileExists = await fsAdapter.exists(agentFilePath);
const agentFileExists = await secureFs
.access(agentFilePath)
.then(() => true)
.catch(() => false);
if (agentFileExists) { if (agentFileExists) {
const definition = await parseAgentFile(agentFilePath, false); const definition = await parseAgentFileWithAdapter(agentFilePath, fsAdapter);
if (definition) { if (definition) {
agents.push({ agents.push({
name: entry.name, name: entry.name,
@@ -206,7 +221,6 @@ async function scanAgentsDirectory(
} }
} }
} }
}
} catch (error) { } catch (error) {
logger.error(`Failed to scan agents directory: ${baseDir}`, error); logger.error(`Failed to scan agents directory: ${baseDir}`, error);
} }