UNPKG

vibe-rules

Version:

A utility for managing Cursor rules, Windsurf rules, and other AI prompts

163 lines 6.95 kB
import * as fs from "fs-extra/esm"; import { writeFile, readFile, readdir } from "fs/promises"; import * as path from "path"; import { StoredRuleConfigSchema } from "../schemas.js"; import { getInternalRuleStoragePath, getCommonRulesDir } from "./path.js"; const RULE_EXTENSION = ".txt"; const STORED_RULE_EXTENSION = ".json"; /** * Saves a rule definition to the internal storage (~/.vibe-rules/<ruleType>/<name>.txt). * @param ruleType - The type of the rule, determining the subdirectory. * @param config - The rule configuration object containing name and content. * @returns The full path where the rule was saved. * @throws Error if file writing fails. */ export async function saveInternalRule(ruleType, config) { const storagePath = getInternalRuleStoragePath(ruleType, config.name); const dir = path.dirname(storagePath); await fs.ensureDir(dir); // Ensure the directory exists await writeFile(storagePath, config.content, "utf-8"); console.debug(`[Rule Storage] Saved internal rule: ${storagePath}`); return storagePath; } /** * Loads a rule definition from the internal storage. * @param ruleType - The type of the rule. * @param name - The name of the rule to load. * @returns The RuleConfig object if found, otherwise null. */ export async function loadInternalRule(ruleType, name) { const storagePath = getInternalRuleStoragePath(ruleType, name); try { if (!(await fs.pathExists(storagePath))) { console.debug(`[Rule Storage] Internal rule not found: ${storagePath}`); return null; } const content = await readFile(storagePath, "utf-8"); console.debug(`[Rule Storage] Loaded internal rule: ${storagePath}`); return { name, content }; } catch (error) { // Log other errors but still return null as the rule couldn't be loaded console.error(`[Rule Storage] Error loading internal rule ${name} (${ruleType}): ${error.message}`); return null; } } /** * Lists the names of all rules stored internally for a given rule type. * @param ruleType - The type of the rule. * @returns An array of rule names. */ export async function listInternalRules(ruleType) { // Use getInternalRuleStoragePath with a dummy name to get the directory path const dummyPath = getInternalRuleStoragePath(ruleType, "__dummy__"); const storageDir = path.dirname(dummyPath); try { if (!(await fs.pathExists(storageDir))) { console.debug(`[Rule Storage] Internal rule directory not found for ${ruleType}: ${storageDir}`); return []; // Directory doesn't exist, no rules } const files = await readdir(storageDir); const ruleNames = files .filter((file) => file.endsWith(RULE_EXTENSION)) .map((file) => path.basename(file, RULE_EXTENSION)); console.debug(`[Rule Storage] Listed ${ruleNames.length} internal rules for ${ruleType}`); return ruleNames; } catch (error) { console.error(`[Rule Storage] Error listing internal rules for ${ruleType}: ${error.message}`); return []; // Return empty list on error } } // --- Common Rule Storage Functions (for user-saved rules with metadata) --- /** * Saves a rule with metadata to the common storage (~/.vibe-rules/rules/<name>.json). * @param config - The stored rule configuration object containing name, content, and metadata. * @returns The full path where the rule was saved. * @throws Error if validation or file writing fails. */ export async function saveCommonRule(config) { // Validate the configuration StoredRuleConfigSchema.parse(config); const commonRulesDir = getCommonRulesDir(); const storagePath = path.join(commonRulesDir, `${config.name}${STORED_RULE_EXTENSION}`); await fs.ensureDir(commonRulesDir); await writeFile(storagePath, JSON.stringify(config, null, 2), "utf-8"); console.debug(`[Rule Storage] Saved common rule with metadata: ${storagePath}`); return storagePath; } /** * Loads a rule with metadata from the common storage. * Falls back to legacy .txt format for backwards compatibility. * @param name - The name of the rule to load. * @returns The StoredRuleConfig object if found, otherwise null. */ export async function loadCommonRule(name) { const commonRulesDir = getCommonRulesDir(); // Try new JSON format first const jsonPath = path.join(commonRulesDir, `${name}${STORED_RULE_EXTENSION}`); if (await fs.pathExists(jsonPath)) { try { const content = await readFile(jsonPath, "utf-8"); const parsed = JSON.parse(content); const validated = StoredRuleConfigSchema.parse(parsed); console.debug(`[Rule Storage] Loaded common rule from JSON: ${jsonPath}`); return validated; } catch (error) { console.error(`[Rule Storage] Error parsing JSON rule ${name}: ${error.message}`); return null; } } // Fall back to legacy .txt format const txtPath = path.join(commonRulesDir, `${name}${RULE_EXTENSION}`); if (await fs.pathExists(txtPath)) { try { const content = await readFile(txtPath, "utf-8"); console.debug(`[Rule Storage] Loaded common rule from legacy .txt: ${txtPath}`); return { name, content, metadata: {}, // No metadata in legacy format }; } catch (error) { console.error(`[Rule Storage] Error loading legacy rule ${name}: ${error.message}`); return null; } } console.debug(`[Rule Storage] Common rule not found: ${name}`); return null; } /** * Lists the names of all rules stored in the common storage (both JSON and legacy .txt). * @returns An array of rule names. */ export async function listCommonRules() { const commonRulesDir = getCommonRulesDir(); try { if (!(await fs.pathExists(commonRulesDir))) { console.debug(`[Rule Storage] Common rules directory not found: ${commonRulesDir}`); return []; } const files = await readdir(commonRulesDir); const ruleNames = new Set(); // Add names from both .json and .txt files files.forEach((file) => { if (file.endsWith(STORED_RULE_EXTENSION)) { ruleNames.add(path.basename(file, STORED_RULE_EXTENSION)); } else if (file.endsWith(RULE_EXTENSION)) { ruleNames.add(path.basename(file, RULE_EXTENSION)); } }); const result = Array.from(ruleNames); console.debug(`[Rule Storage] Listed ${result.length} common rules`); return result; } catch (error) { console.error(`[Rule Storage] Error listing common rules: ${error.message}`); return []; } } //# sourceMappingURL=rule-storage.js.map