/** * SOP file discovery for the Ion workflow engine. * * Locates `.sop.md` files by delegating file-system traversal to a * caller-provided glob function. This keeps the module pure (no Node * dependency) and easily testable. */ // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- /** * A function that resolves a glob pattern to an array of absolute paths. * * The caller typically provides an implementation backed by `node:fs/promises` * or a test double. */ export type GlobFn = (pattern: string) => Promise; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- /** Default search directories (in priority order, relative to cwd). */ const SEARCH_DIRS = ['.archon/workflows', '.']; /** Glob pattern for SOP markdown files. */ const SOP_GLOB = '**/*.sop.md'; // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- /** * Discover all `.sop.md` files in the given working directory. * * Searches `.archon/workflows/` first, then the project root, and returns * absolute paths to every matching file. Duplicate paths are de-duplicated. * * @param cwd - The working directory to search from. * @param globFn - A function that resolves a glob pattern to file paths. * Typically backed by `glob` from `node:fs/promises` or a * similar library. * @returns An array of absolute file paths to discovered `.sop.md` files, * sorted deterministically. */ export async function discoverSopFiles( cwd: string, globFn: GlobFn, ): Promise { const seen = new Set(); const results: string[] = []; for (const dir of SEARCH_DIRS) { const pattern = dir === '.' ? `${cwd}/${SOP_GLOB}` : `${cwd}/${dir}/${SOP_GLOB}`; let paths: string[]; try { paths = await globFn(pattern); } catch { // Glob errors (e.g. directory doesn't exist) are non-fatal. continue; } // Sort for deterministic output and de-duplicate paths.sort(); for (const p of paths) { if (!seen.has(p)) { seen.add(p); results.push(p); } } } return results; }