UNPKG

vibe-codex

Version:

CLI tool to install development rules and git hooks with interactive configuration

301 lines (275 loc) 8.53 kB
/** * Core module - Essential rules that are always required */ import { RuleModule } from "../base.js"; import fs from "fs/promises"; import path from "path"; import { fileURLToPath } from "url"; import logger from "../../utils/logger.js"; import { calculateSimilarity, checkForSecrets } from "./utils.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export class CoreModule extends RuleModule { constructor() { super({ name: "core", version: "1.0.0", description: "Essential security, workflow, and git rules", dependencies: [], options: {}, }); } async loadRules() { // Level 1: Security Rules this.registerRule({ id: "SEC-1", name: "No Secrets in Code", description: "Never commit secrets, API keys, passwords, or credentials", level: 1, category: "security", severity: "error", check: async (context) => { const violations = []; for (const file of context.files) { const secrets = checkForSecrets(file.content); for (const secret of secrets) { violations.push({ file: file.path, line: secret.line, message: "Potential secret detected", }); } } return violations; }, }); this.registerRule({ id: "SEC-2", name: "Environment File Protection", description: "Never overwrite environment files", level: 1, category: "security", severity: "error", check: async (context) => { const violations = []; const envFiles = [".env", ".env.local", ".env.production"]; for (const file of context.modifiedFiles) { if (envFiles.includes(path.basename(file.path))) { violations.push({ file: file.path, message: "Environment files should not be modified directly", }); } } return violations; }, }); this.registerRule({ id: "SEC-3", name: "Environment Example File", description: "Always create .env.example with documented variables", level: 1, category: "security", severity: "warning", check: async (context) => { const hasEnvFile = context.files.some( (f) => f.path.endsWith(".env") || f.path.endsWith(".env.local"), ); const hasEnvExample = context.files.some((f) => f.path.endsWith(".env.example"), ); if (hasEnvFile && !hasEnvExample) { return [ { message: "Missing .env.example file", }, ]; } return []; }, }); // Level 2: Workflow Rules this.registerRule({ id: "WF-1", name: "Issue-First Development", description: "Every code change must start with a GitHub issue", level: 2, category: "workflow", severity: "error", check: async (context) => { if (!context.issue) { return [ { message: "No associated GitHub issue found", }, ]; } return []; }, }); this.registerRule({ id: "WF-2", name: "Branch Naming Convention", description: "Branch must reference issue: feature/issue-{number}-{description}", level: 2, category: "workflow", severity: "error", check: async (context) => { const branchPattern = /^(feature|bugfix|hotfix)\/issue-\d+-[\w-]+$/; if (!branchPattern.test(context.branch)) { return [ { branch: context.branch, message: "Branch name must follow pattern: {type}/issue-{number}-{description}", }, ]; } return []; }, }); this.registerRule({ id: "WF-3", name: "Commit Message Format", description: "Commits must be clear and reference issues", level: 2, category: "workflow", severity: "warning", check: async (context) => { const violations = []; const pattern = /^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?:\s.+/; for (const commit of context.commits) { if (!pattern.test(commit.message)) { violations.push({ commit: commit.sha.substring(0, 7), message: "Commit message should follow conventional format", }); } } return violations; }, }); this.registerRule({ id: "WF-4", name: "PR Title References Issue", description: "PR title must reference the issue number", level: 2, category: "workflow", severity: "error", check: async (context) => { if (!context.pr) return []; const hasIssueRef = /#\d+/.test(context.pr.title); if (!hasIssueRef) { return [ { message: "PR title must reference issue number (e.g., #123)", }, ]; } return []; }, }); this.registerRule({ id: "WF-5", name: "Token Efficiency", description: "Consolidate redundant content for LLM efficiency", level: 2, category: "workflow", severity: "warning", check: async (context) => { const violations = []; const contentMap = new Map(); // Check for duplicate content for (const file of context.files) { const content = file.content.toLowerCase().replace(/\s+/g, " "); for (const [otherFile, otherContent] of contentMap) { const similarity = calculateSimilarity(content, otherContent); if (similarity > 0.25 && file.path !== otherFile) { violations.push({ files: [file.path, otherFile], similarity: Math.round(similarity * 100), message: `Files have ${Math.round(similarity * 100)}% similar content`, }); } } contentMap.set(file.path, content); } return violations; }, }); } async loadHooks() { // Pre-commit hook this.registerHook("pre-commit", async (context) => { logger.info("🔍 Running security pre-commit checks..."); // Check for secrets const secretsRule = this.rules.find((r) => r.id === "SEC-1"); if (secretsRule) { const violations = await secretsRule.check(context); if (violations.length > 0) { logger.error("❌ Security violations found:"); violations.forEach((v) => logger.error(` - ${v.file}: ${v.message}`), ); return false; } } logger.success("✅ No secrets detected"); return true; }); // Commit-msg hook this.registerHook("commit-msg", async (context) => { const pattern = /^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?:\s.+/; if (!pattern.test(context.message)) { logger.error("❌ Invalid commit message format!"); logger.error("Expected format: <type>(<scope>): <subject>"); logger.error( "Valid types: feat, fix, docs, style, refactor, test, chore", ); return false; } return true; }); } async loadValidators() { // Environment validator this.registerValidator("environment", async (projectPath) => { const envPath = path.join(projectPath, ".env"); const envExamplePath = path.join(projectPath, ".env.example"); try { await fs.access(envPath); // .env exists, check for .env.example try { await fs.access(envExamplePath); return { valid: true }; } catch { return { valid: false, message: ".env file exists but .env.example is missing", }; } } catch { // .env doesn't exist, which is fine return { valid: true }; } }); // Git workflow validator this.registerValidator("git-workflow", async (context) => { const errors = []; // Check if in a git repository try { await fs.access(path.join(context.projectPath, ".git")); } catch { errors.push("Not a git repository"); } return { valid: errors.length === 0, errors, }; }); } } // Export singleton instance export default new CoreModule();