rulix 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/danielchinome/develop-projects/side-projects/rulix/dist/chunk-I6MHHT6P.cjs","../src/adapters/agents-md.ts","../src/core/tokenizer.ts","../src/adapters/claude-code.ts","../src/adapters/cursor.ts","../src/adapters/registry.ts","../src/core/ir.ts","../src/core/config.ts","../src/core/parser.ts","../src/core/validator.ts"],"names":["access","writeFile","join","mkdir","rm","RULES_DIR","stripQuotes","parseInlineArray","splitFrontmatter"],"mappings":"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AA;AAID,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAES;AACR,EAAA;AACO,IAAA;AACA,IAAA;AACF,IAAA;AAEE,IAAA;AACA,IAAA;AAEF,IAAA;AACC,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AASS;AACF,EAAA;AACF,EAAA;AACE,EAAA;AAEN,EAAA;AACM,IAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACM,IAAA;AACF,IAAA;AACH,MAAA;AACM,IAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AACD,EAAA;AAES,EAAA;AACV;AAIS;AAQD,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACI,IAAA;AACP,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAES;AACF,EAAA;AACF,EAAA;AAEI,EAAA;AAEJ,EAAA;AACE,IAAA;AACE,IAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AAEM,EAAA;AACF,EAAA;AACG,IAAA;AACL,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AAEA,EAAA;AACO,IAAA;AACA,IAAA;AAGA,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACL,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AAEO,EAAA;AACR;AAEA;AACO,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AAED,EAAA;AACE,IAAA;AACC,IAAA;AACR,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACM,IAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAEA;AACO,EAAA;AACA,EAAA;AACA,EAAA;AAEN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AAEO,EAAA;AACR;AAIS;AACF,EAAA;AACA,EAAA;AAEN,EAAA;AACO,IAAA;AACN,IAAA;AAA6C;AAAY;AAC1D,EAAA;AAEU,EAAA;AAAqB;AAChC;AAES;AACE,EAAA;AACF,IAAA;AAAe;AACvB,EAAA;AAEM,EAAA;AACG,EAAA;AACF,IAAA;AACF,IAAA;AACE,EAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAEA;AAKO,EAAA;AACE,IAAA;AACR,EAAA;AACI,EAAA;AAEC,EAAA;AACE,IAAA;AACAC,IAAAA;AACP,EAAA;AACQ,EAAA;AACT;AAEA;AAKO,EAAA;AACF,EAAA;AAEE,EAAA;AACD,EAAA;AAEC,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACEA,MAAAA;AACLC,QAAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACQ,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAEA;AAKO,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACP,EAAA;AACM,EAAA;AAEN,EAAA;AACM,IAAA;AACE,MAAA;AACD,MAAA;AACL,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAIa;AACN,EAAA;AACN,EAAA;AAEM,EAAA;AACC,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AAEM,EAAA;AACC,IAAA;AACA,IAAA;AAEC,IAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AAEM,EAAA;AAKC,IAAA;AACA,IAAA;AAEA,IAAA;AACA,IAAA;AACA,IAAA;AAKC,IAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AAEA,EAAA;AACQ,IAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AHWY;AACA;AI1aZ;AACCF;AACAG;AACA;AACA;AACAC;AACA;AACM;AACE;AAcHC;AACA;AACA;AAIN;AACK,EAAA;AACGL,IAAAA;AACC,IAAA;AACA,EAAA;AACA,IAAA;AACR,EAAA;AACD;AAEA;AACK,EAAA;AACG,IAAA;AACC,IAAA;AACA,EAAA;AACC,IAAA;AACT,EAAA;AACD;AAISM;AACE,EAAA;AAEN,EAAA;AAGI,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAOS;AACF,EAAA;AACI,EAAA;AAED,EAAA;AACJ,IAAA;AACH,MAAA;AACC,QAAA;AACA,QAAA;AAID,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAQS;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AAEJ,EAAA;AACO,IAAA;AACF,IAAA;AACE,IAAA;AACF,IAAA;AAEE,IAAA;AACA,IAAA;AAEF,IAAA;AACH,MAAA;AACD,IAAA;AACC,MAAA;AAID,IAAA;AACC,MAAA;AACD,IAAA;AACD,EAAA;AAES,EAAA;AACV;AAIS;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAIS;AAKF,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAES;AAKF,EAAA;AACA,EAAA;AACA,EAAA;AAEC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACI,IAAA;AACP,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAEA;AAGO,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAED,IAAA;AACE,MAA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,MAAA;AACL,IAAA;AACD,EAAA;AACD;AAES;AACE,EAAA;AACH,IAAA;AACF,IAAA;AACJ,IAAA;AACD,EAAA;AACM,EAAA;AACN,EAAA;AACO,IAAA;AACP,EAAA;AACD;AAGgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAKA;AACO,EAAA;AAEF,EAAA;AACA,EAAA;AACH,IAAA;AACQ,EAAA;AACJ,IAAA;AACH,MAAA;AACD,IAAA;AACM,IAAA;AACP,EAAA;AAEM,EAAA;AACA,EAAA;AAEN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACP,EAAA;AAES,EAAA;AACV;AAGA;AAIO,EAAA;AACAL,EAAAA;AACAF,EAAAA;AACP;ARuuBY;AACA;ASh/BN;AACA;AAEA;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAIS;AAOD,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACA,IAAA;AACL,EAAA;AACD;AAIS;AACF,EAAA;AACA,EAAA;AAEN,EAAA;AACO,IAAA;AACD,IAAA;AACD,IAAA;AACH,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AAEO,EAAA;AACR;AAIS;AACF,EAAA;AACG,EAAA;AACD,IAAA;AACR,EAAA;AACS,EAAA;AACD,IAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAES;AACC,EAAA;AACC,EAAA;AACF,IAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACQ,EAAA;AACT;AAES;AACC,EAAA;AACD,IAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACQ,EAAA;AACT;AAES;AACR,EAAA;AACK,IAAA;AACH,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACQ,EAAA;AACT;AAES;AACC,EAAA;AACD,IAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACQ,EAAA;AACT;AAES;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACK,IAAA;AAAY,IAAA;AACK,IAAA;AACA,IAAA;AAEjB,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACE,EAAA;AACJ,EAAA;AACN,EAAA;AACM,IAAA;AACJ,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACI,IAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACQ,EAAA;AACT;AAIS;AACD,EAAA;AACE,IAAA;AACA,IAAA;AACR,IAAA;AACM,IAAA;AACP,EAAA;AACD;AAKgB;AACT,EAAA;AAEC,EAAA;AAEP,EAAA;AACQ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AAEO,EAAA;AACR;AT+8BY;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/Users/danielchinome/develop-projects/side-projects/rulix/dist/chunk-I6MHHT6P.cjs","sourcesContent":[null,"/**\n * AGENTS.md adapter: export-only.\n *\n * Import is not supported in v0.1 because AGENTS.md is too\n * unstructured to parse reliably into discrete rules.\n */\n\nimport { access, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type {\n\tExportOptions,\n\tExportResult,\n\tImportResult,\n\tRule,\n\tRuleCategory,\n\tRulixAdapter,\n\tTokenBudget,\n} from \"../core/ir.js\";\n\nconst AGENTS_MD = \"AGENTS.md\";\nconst GENERATED_HEADER =\n\t\"<!-- Generated by Rulix. Do not edit directly. -->\\n\\n\";\n\nconst CATEGORY_LABELS: Record<RuleCategory, string> = {\n\tstyle: \"Style\",\n\tsecurity: \"Security\",\n\ttesting: \"Testing\",\n\tarchitecture: \"Architecture\",\n\tworkflow: \"Workflow\",\n\tgeneral: \"General\",\n};\n\nconst CATEGORY_ORDER: RuleCategory[] = [\n\t\"architecture\",\n\t\"style\",\n\t\"security\",\n\t\"testing\",\n\t\"workflow\",\n\t\"general\",\n];\n\n// ─── Filesystem Helpers ──────────────────────────────────────────\n\nasync function pathExists(filePath: string): Promise<boolean> {\n\ttry {\n\t\tawait access(filePath);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// ─── Export Helpers ──────────────────────────────────────────────\n\nfunction groupByCategory(rules: Rule[]): Map<RuleCategory, Rule[]> {\n\tconst groups = new Map<RuleCategory, Rule[]>();\n\n\tfor (const rule of rules) {\n\t\tconst existing = groups.get(rule.category);\n\t\tif (existing) {\n\t\t\texisting.push(rule);\n\t\t} else {\n\t\t\tgroups.set(rule.category, [rule]);\n\t\t}\n\t}\n\n\treturn groups;\n}\n\nfunction buildCategorySection(category: RuleCategory, rules: Rule[]): string {\n\tconst sorted = [...rules].sort((a, b) => a.priority - b.priority);\n\tconst label = CATEGORY_LABELS[category];\n\tconst sections = sorted.map((r) => `### ${r.description}\\n\\n${r.content}`);\n\treturn `## ${label}\\n\\n${sections.join(\"\\n\\n\")}`;\n}\n\nfunction buildAgentsMdContent(rules: Rule[], includeHeader: boolean): string {\n\tconst exportable = rules.filter(\n\t\t(r) => r.scope === \"always\" || r.scope === \"file-scoped\",\n\t);\n\tif (exportable.length === 0) return \"\";\n\n\tconst groups = groupByCategory(exportable);\n\tconst sections: string[] = [];\n\n\tfor (const category of CATEGORY_ORDER) {\n\t\tconst categoryRules = groups.get(category);\n\t\tif (categoryRules && categoryRules.length > 0) {\n\t\t\tsections.push(buildCategorySection(category, categoryRules));\n\t\t}\n\t}\n\n\tconst header = includeHeader ? GENERATED_HEADER : \"\";\n\treturn `${header}# AGENTS.md\\n\\n${sections.join(\"\\n\\n\")}\\n`;\n}\n\n// ─── Adapter ─────────────────────────────────────────────────────\n\nexport const agentsMdAdapter: RulixAdapter = {\n\tname: \"agents-md\",\n\tdisplayName: \"AGENTS.md\",\n\n\tasync detect(projectRoot: string): Promise<boolean> {\n\t\treturn pathExists(join(projectRoot, AGENTS_MD));\n\t},\n\n\tasync import(_projectRoot: string): Promise<ImportResult> {\n\t\treturn { rules: [], warnings: [], source: AGENTS_MD };\n\t},\n\n\tasync export(\n\t\trules: Rule[],\n\t\tprojectRoot: string,\n\t\toptions?: ExportOptions,\n\t): Promise<ExportResult> {\n\t\tconst dryRun = options?.dryRun === true;\n\t\tconst content = buildAgentsMdContent(rules, true);\n\n\t\tif (content === \"\") {\n\t\t\treturn { filesWritten: [], filesDeleted: [], warnings: [] };\n\t\t}\n\n\t\tif (!dryRun) {\n\t\t\tawait writeFile(join(projectRoot, AGENTS_MD), content, \"utf-8\");\n\t\t}\n\n\t\treturn {\n\t\t\tfilesWritten: [AGENTS_MD],\n\t\t\tfilesDeleted: [],\n\t\t\twarnings: [],\n\t\t};\n\t},\n\n\tgetTokenBudget(): TokenBudget {\n\t\treturn {\n\t\t\tmaxTokens: 32_768,\n\t\t\tmaxInstructions: 0,\n\t\t\twarningThreshold: 0.8,\n\t\t\tsource: \"AGENTS.md convention\",\n\t\t};\n\t},\n};\n","/**\n * Lightweight token estimation without external dependencies.\n *\n * Uses a character-based heuristic (~4 chars per token) that's accurate\n * enough for budget warnings. Exact counting via tiktoken is planned for v0.2.\n */\n\nconst CHARS_PER_TOKEN = 4;\n\n/** Estimates token count using the ~4 chars/token heuristic for English/code. */\nexport function estimateTokens(text: string): number {\n\tif (text.length === 0) return 0;\n\treturn Math.ceil(text.length / CHARS_PER_TOKEN);\n}\n\n/**\n * Builds the full text that a rule contributes to a tool's context window:\n * frontmatter metadata + markdown content.\n */\nexport function estimateRuleTokens(\n\tcontent: string,\n\tdescription: string,\n): number {\n\treturn estimateTokens(`${description}\\n${content}`);\n}\n\n/** Sums estimated tokens across multiple content strings. */\nexport function sumTokens(tokenCounts: number[]): number {\n\tlet total = 0;\n\tfor (const count of tokenCounts) {\n\t\ttotal += count;\n\t}\n\treturn total;\n}\n\nexport interface TokenBudgetUsage {\n\treadonly used: number;\n\treadonly max: number;\n\treadonly percentage: number;\n\treadonly exceeded: boolean;\n}\n\n/** Computes usage against a budget, returning percentage and exceeded flag. */\nexport function computeBudgetUsage(\n\tused: number,\n\tmax: number,\n): TokenBudgetUsage {\n\tconst percentage = max === 0 ? 100 : (used / max) * 100;\n\treturn {\n\t\tused,\n\t\tmax,\n\t\tpercentage,\n\t\texceeded: used > max,\n\t};\n}\n","/**\n * Claude Code adapter: imports from `CLAUDE.md` and `.claude/rules/*.md`,\n * exports IR rules to Claude Code format.\n *\n * Never touches `.claude/skills/`, `.claude/commands/`,\n * `.claude/agents/`, or `.claude/settings.json`.\n */\n\nimport {\n\taccess,\n\tmkdir,\n\treaddir,\n\treadFile,\n\trm,\n\twriteFile,\n} from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport type {\n\tExportOptions,\n\tExportResult,\n\tImportResult,\n\tRule,\n\tRuleScope,\n\tRulixAdapter,\n\tTokenBudget,\n} from \"../core/ir.js\";\nimport { estimateRuleTokens } from \"../core/tokenizer.js\";\n\nconst CLAUDE_MD = \"CLAUDE.md\";\nconst RULES_DIR = \".claude/rules\";\nconst CONTEXT_PREFIX = \"Context: \";\n\n// ─── Filesystem Helpers ──────────────────────────────────────────\n\nasync function pathExists(filePath: string): Promise<boolean> {\n\ttry {\n\t\tawait access(filePath);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function listMdFiles(dir: string): Promise<string[]> {\n\ttry {\n\t\tconst entries = await readdir(dir);\n\t\treturn entries.filter((f) => f.endsWith(\".md\")).sort();\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n// ─── Text Helpers ────────────────────────────────────────────────\n\nfunction toKebabCase(text: string): string {\n\treturn text\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\n\t\t.replace(/^-|-$/g, \"\");\n}\n\nfunction stripQuotes(s: string): string {\n\tconst t = s.trim();\n\tif (\n\t\t(t.startsWith('\"') && t.endsWith('\"')) ||\n\t\t(t.startsWith(\"'\") && t.endsWith(\"'\"))\n\t) {\n\t\treturn t.slice(1, -1);\n\t}\n\treturn t;\n}\n\n// ─── Frontmatter Parsing ─────────────────────────────────────────\n\ninterface FmParts {\n\treadonly yaml: string;\n\treadonly content: string;\n}\n\nfunction splitFrontmatter(raw: string): FmParts | null {\n\tconst lines = raw.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n\tif (lines[0]?.trim() !== \"---\") return null;\n\n\tfor (let i = 1; i < lines.length; i++) {\n\t\tif (lines[i]?.trim() === \"---\") {\n\t\t\treturn {\n\t\t\t\tyaml: lines.slice(1, i).join(\"\\n\"),\n\t\t\t\tcontent: lines\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t.trim(),\n\t\t\t};\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction parsePaths(yaml: string): string[] | undefined {\n\tfor (const line of yaml.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tconst colonIdx = trimmed.indexOf(\":\");\n\t\tif (colonIdx === -1) continue;\n\n\t\tconst key = trimmed.slice(0, colonIdx).trim();\n\t\tconst value = trimmed.slice(colonIdx + 1).trim();\n\n\t\tif (key === \"paths\" && value !== \"\") {\n\t\t\tif (value.startsWith(\"[\") && value.endsWith(\"]\")) {\n\t\t\t\tconst inner = value.slice(1, -1).trim();\n\t\t\t\treturn inner === \"\" ? [] : inner.split(\",\").map(stripQuotes);\n\t\t\t}\n\t\t\treturn [stripQuotes(value)];\n\t\t}\n\t}\n\treturn undefined;\n}\n\n// ─── CLAUDE.md Splitting ─────────────────────────────────────────\n\ninterface H2Section {\n\treadonly heading: string;\n\treadonly content: string;\n}\n\nfunction splitByH2(raw: string): { preamble: string; sections: H2Section[] } {\n\tconst parts = raw.split(/^(?=## )/m);\n\tlet preamble = \"\";\n\tconst sections: H2Section[] = [];\n\n\tfor (const part of parts) {\n\t\tif (!part.startsWith(\"## \")) {\n\t\t\tpreamble = part.trim();\n\t\t\tcontinue;\n\t\t}\n\t\tconst newlineIdx = part.indexOf(\"\\n\");\n\t\tif (newlineIdx === -1) {\n\t\t\tsections.push({ heading: part.slice(3).trim(), content: \"\" });\n\t\t} else {\n\t\t\tsections.push({\n\t\t\t\theading: part.slice(3, newlineIdx).trim(),\n\t\t\t\tcontent: part.slice(newlineIdx + 1).trim(),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn { preamble, sections };\n}\n\n// ─── Import Helpers ──────────────────────────────────────────────\n\nfunction createImportedRule(\n\tid: string,\n\tscope: RuleScope,\n\tdescription: string,\n\tcontent: string,\n\tfilePath: string,\n\tglobs?: string[],\n): Rule {\n\treturn {\n\t\tid,\n\t\tscope,\n\t\tdescription,\n\t\tcontent,\n\t\tcategory: \"general\",\n\t\tpriority: 3,\n\t\testimatedTokens: estimateRuleTokens(content, description),\n\t\t...(globs ? { globs } : {}),\n\t\tsource: {\n\t\t\tadapter: \"claude-code\",\n\t\t\tfilePath,\n\t\t\timportedAt: new Date().toISOString(),\n\t\t},\n\t};\n}\n\nfunction importClaudeMdContent(raw: string): Rule[] {\n\tconst content = raw.replace(/\\r\\n/g, \"\\n\").trim();\n\tif (content === \"\") return [];\n\n\tconst { preamble, sections } = splitByH2(content);\n\n\tif (sections.length === 0) {\n\t\tif (!preamble) return [];\n\t\treturn [\n\t\t\tcreateImportedRule(\n\t\t\t\t\"claude-md\",\n\t\t\t\t\"always\",\n\t\t\t\t\"CLAUDE.md contents\",\n\t\t\t\tpreamble,\n\t\t\t\tCLAUDE_MD,\n\t\t\t),\n\t\t];\n\t}\n\n\tconst rules: Rule[] = [];\n\tif (preamble) {\n\t\trules.push(\n\t\t\tcreateImportedRule(\n\t\t\t\t\"claude-md-preamble\",\n\t\t\t\t\"always\",\n\t\t\t\t\"CLAUDE.md preamble\",\n\t\t\t\tpreamble,\n\t\t\t\tCLAUDE_MD,\n\t\t\t),\n\t\t);\n\t}\n\n\tfor (const section of sections) {\n\t\tconst isContext = section.heading.startsWith(CONTEXT_PREFIX);\n\t\tconst description = isContext\n\t\t\t? section.heading.slice(CONTEXT_PREFIX.length)\n\t\t\t: section.heading;\n\t\tconst scope: RuleScope = isContext ? \"agent-selected\" : \"always\";\n\t\tconst id = toKebabCase(description);\n\t\tif (id && section.content) {\n\t\t\trules.push(\n\t\t\t\tcreateImportedRule(id, scope, description, section.content, CLAUDE_MD),\n\t\t\t);\n\t\t}\n\t}\n\n\treturn rules;\n}\n\nasync function importClaudeMd(projectRoot: string): Promise<Rule[]> {\n\tconst filePath = join(projectRoot, CLAUDE_MD);\n\tif (!(await pathExists(filePath))) return [];\n\tconst raw = await readFile(filePath, \"utf-8\");\n\treturn importClaudeMdContent(raw);\n}\n\nfunction importClaudeRuleFile(raw: string, filePath: string, id: string): Rule {\n\tconst description = id.replace(/-/g, \" \");\n\tconst parts = splitFrontmatter(raw);\n\n\tif (!parts) {\n\t\tconst content = raw.replace(/\\r\\n/g, \"\\n\").trim();\n\t\treturn createImportedRule(id, \"always\", description, content, filePath);\n\t}\n\n\tconst paths = parsePaths(parts.yaml);\n\tconst scope: RuleScope = paths && paths.length > 0 ? \"file-scoped\" : \"always\";\n\tconst globs = scope === \"file-scoped\" ? paths : undefined;\n\treturn createImportedRule(\n\t\tid,\n\t\tscope,\n\t\tdescription,\n\t\tparts.content,\n\t\tfilePath,\n\t\tglobs,\n\t);\n}\n\nasync function importClaudeRuleFiles(projectRoot: string): Promise<Rule[]> {\n\tconst dir = join(projectRoot, RULES_DIR);\n\tconst files = await listMdFiles(dir);\n\tconst rules: Rule[] = [];\n\n\tfor (const file of files) {\n\t\tconst filePath = join(RULES_DIR, file);\n\t\tconst raw = await readFile(join(projectRoot, filePath), \"utf-8\");\n\t\trules.push(importClaudeRuleFile(raw, filePath, basename(file, \".md\")));\n\t}\n\n\treturn rules;\n}\n\n// ─── Export Helpers ──────────────────────────────────────────────\n\nfunction buildClaudeMdContent(rules: Rule[]): string {\n\tconst sorted = [...rules].sort((a, b) => a.priority - b.priority);\n\tconst sections: string[] = [];\n\n\tfor (const rule of sorted) {\n\t\tconst prefix = rule.scope === \"agent-selected\" ? CONTEXT_PREFIX : \"\";\n\t\tsections.push(`## ${prefix}${rule.description}\\n\\n${rule.content}`);\n\t}\n\n\treturn `${sections.join(\"\\n\\n\")}\\n`;\n}\n\nfunction buildClaudeRuleFile(rule: Rule): string {\n\tif (!rule.globs || rule.globs.length === 0) {\n\t\treturn `${rule.content}\\n`;\n\t}\n\n\tconst lines: string[] = [\"---\"];\n\tif (rule.globs.length === 1) {\n\t\tconst first = rule.globs[0];\n\t\tif (first !== undefined) lines.push(`paths: \"${first}\"`);\n\t} else {\n\t\tconst items = rule.globs.map((g) => `\"${g}\"`).join(\", \");\n\t\tlines.push(`paths: [${items}]`);\n\t}\n\tlines.push(\"---\");\n\tlines.push(\"\");\n\tlines.push(rule.content);\n\tlines.push(\"\");\n\treturn lines.join(\"\\n\");\n}\n\nasync function exportClaudeMd(\n\trules: Rule[],\n\tprojectRoot: string,\n\tdryRun: boolean,\n): Promise<string[]> {\n\tconst mdRules = rules.filter(\n\t\t(r) => r.scope === \"always\" || r.scope === \"agent-selected\",\n\t);\n\tif (mdRules.length === 0) return [];\n\n\tif (!dryRun) {\n\t\tconst content = buildClaudeMdContent(mdRules);\n\t\tawait writeFile(join(projectRoot, CLAUDE_MD), content, \"utf-8\");\n\t}\n\treturn [CLAUDE_MD];\n}\n\nasync function exportClaudeRules(\n\trules: Rule[],\n\tprojectRoot: string,\n\tdryRun: boolean,\n): Promise<string[]> {\n\tconst fileScoped = rules.filter((r) => r.scope === \"file-scoped\");\n\tif (fileScoped.length === 0) return [];\n\n\tconst dir = join(projectRoot, RULES_DIR);\n\tif (!dryRun) await mkdir(dir, { recursive: true });\n\n\tconst written: string[] = [];\n\tfor (const rule of fileScoped) {\n\t\tconst filePath = join(RULES_DIR, `${rule.id}.md`);\n\t\tif (!dryRun) {\n\t\t\tawait writeFile(\n\t\t\t\tjoin(projectRoot, filePath),\n\t\t\t\tbuildClaudeRuleFile(rule),\n\t\t\t\t\"utf-8\",\n\t\t\t);\n\t\t}\n\t\twritten.push(filePath);\n\t}\n\treturn written;\n}\n\nasync function deleteStaleRuleFiles(\n\trules: Rule[],\n\tprojectRoot: string,\n\tdryRun: boolean,\n): Promise<string[]> {\n\tconst dir = join(projectRoot, RULES_DIR);\n\tconst existing = await listMdFiles(dir);\n\tconst exportedIds = new Set(\n\t\trules.filter((r) => r.scope === \"file-scoped\").map((r) => `${r.id}.md`),\n\t);\n\tconst deleted: string[] = [];\n\n\tfor (const file of existing) {\n\t\tif (!exportedIds.has(file)) {\n\t\t\tconst filePath = join(RULES_DIR, file);\n\t\t\tif (!dryRun) await rm(join(projectRoot, filePath));\n\t\t\tdeleted.push(filePath);\n\t\t}\n\t}\n\treturn deleted;\n}\n\n// ─── Adapter ─────────────────────────────────────────────────────\n\nexport const claudeCodeAdapter: RulixAdapter = {\n\tname: \"claude-code\",\n\tdisplayName: \"Claude Code\",\n\n\tasync detect(projectRoot: string): Promise<boolean> {\n\t\tconst claudeMd = join(projectRoot, CLAUDE_MD);\n\t\tconst claudeDir = join(projectRoot, \".claude\");\n\t\treturn (await pathExists(claudeMd)) || (await pathExists(claudeDir));\n\t},\n\n\tasync import(projectRoot: string): Promise<ImportResult> {\n\t\tconst mdRules = await importClaudeMd(projectRoot);\n\t\tconst ruleFiles = await importClaudeRuleFiles(projectRoot);\n\n\t\treturn {\n\t\t\trules: [...mdRules, ...ruleFiles],\n\t\t\twarnings: [],\n\t\t\tsource: CLAUDE_MD,\n\t\t};\n\t},\n\n\tasync export(\n\t\trules: Rule[],\n\t\tprojectRoot: string,\n\t\toptions?: ExportOptions,\n\t): Promise<ExportResult> {\n\t\tconst dryRun = options?.dryRun === true;\n\t\tconst strategy = options?.strategy ?? \"overwrite\";\n\n\t\tconst mdWritten = await exportClaudeMd(rules, projectRoot, dryRun);\n\t\tconst rulesWritten = await exportClaudeRules(rules, projectRoot, dryRun);\n\t\tconst filesDeleted =\n\t\t\tstrategy === \"overwrite\"\n\t\t\t\t? await deleteStaleRuleFiles(rules, projectRoot, dryRun)\n\t\t\t\t: [];\n\n\t\treturn {\n\t\t\tfilesWritten: [...mdWritten, ...rulesWritten],\n\t\t\tfilesDeleted,\n\t\t\twarnings: [],\n\t\t};\n\t},\n\n\tgetTokenBudget(): TokenBudget {\n\t\treturn {\n\t\t\tmaxTokens: 2_000,\n\t\t\tmaxInstructions: 150,\n\t\t\twarningThreshold: 0.8,\n\t\t\tsource: \"Claude Code documentation\",\n\t\t};\n\t},\n};\n","/**\n * Cursor adapter: imports from `.cursor/rules/*.mdc` and `.cursorrules`,\n * exports IR rules to `.cursor/rules/*.mdc`.\n */\n\nimport {\n\taccess,\n\tmkdir,\n\treaddir,\n\treadFile,\n\trm,\n\twriteFile,\n} from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport type {\n\tExportOptions,\n\tExportResult,\n\tExportWarning,\n\tImportResult,\n\tImportWarning,\n\tRule,\n\tRuleScope,\n\tRulixAdapter,\n\tTokenBudget,\n} from \"../core/ir.js\";\nimport { estimateRuleTokens } from \"../core/tokenizer.js\";\n\nconst RULES_DIR = \".cursor/rules\";\nconst LEGACY_FILE = \".cursorrules\";\nconst MDC_EXT = \".mdc\";\n\n// ─── Filesystem Helpers ──────────────────────────────────────────\n\nasync function pathExists(filePath: string): Promise<boolean> {\n\ttry {\n\t\tawait access(filePath);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function listMdcFiles(dir: string): Promise<string[]> {\n\ttry {\n\t\tconst entries = await readdir(dir);\n\t\treturn entries.filter((f) => f.endsWith(MDC_EXT)).sort();\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n// ─── Frontmatter Parsing ─────────────────────────────────────────\n\nfunction stripQuotes(s: string): string {\n\tconst t = s.trim();\n\tif (\n\t\t(t.startsWith('\"') && t.endsWith('\"')) ||\n\t\t(t.startsWith(\"'\") && t.endsWith(\"'\"))\n\t) {\n\t\treturn t.slice(1, -1);\n\t}\n\treturn t;\n}\n\nfunction parseInlineArray(raw: string): string[] {\n\tconst inner = raw.slice(1, -1).trim();\n\tif (inner === \"\") return [];\n\treturn inner.split(\",\").map(stripQuotes);\n}\n\ninterface MdcParts {\n\treadonly yaml: string;\n\treadonly content: string;\n}\n\nfunction splitMdcFrontmatter(raw: string): MdcParts | null {\n\tconst lines = raw.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n\tif (lines[0]?.trim() !== \"---\") return null;\n\n\tfor (let i = 1; i < lines.length; i++) {\n\t\tif (lines[i]?.trim() === \"---\") {\n\t\t\treturn {\n\t\t\t\tyaml: lines.slice(1, i).join(\"\\n\"),\n\t\t\t\tcontent: lines\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t.trim(),\n\t\t\t};\n\t\t}\n\t}\n\treturn null;\n}\n\ninterface MdcFields {\n\treadonly description: string | undefined;\n\treadonly globs: string[] | undefined;\n\treadonly alwaysApply: boolean;\n}\n\nfunction parseMdcFields(yaml: string): MdcFields {\n\tlet description: string | undefined;\n\tlet globs: string[] | undefined;\n\tlet alwaysApply = false;\n\n\tfor (const line of yaml.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (trimmed === \"\") continue;\n\t\tconst colonIdx = trimmed.indexOf(\":\");\n\t\tif (colonIdx === -1) continue;\n\n\t\tconst key = trimmed.slice(0, colonIdx).trim();\n\t\tconst value = trimmed.slice(colonIdx + 1).trim();\n\n\t\tif (key === \"description\" && value !== \"\") {\n\t\t\tdescription = stripQuotes(value);\n\t\t} else if (key === \"globs\" && value !== \"\") {\n\t\t\tglobs =\n\t\t\t\tvalue.startsWith(\"[\") && value.endsWith(\"]\")\n\t\t\t\t\t? parseInlineArray(value)\n\t\t\t\t\t: [stripQuotes(value)];\n\t\t} else if (key === \"alwaysApply\") {\n\t\t\talwaysApply = value === \"true\";\n\t\t}\n\t}\n\n\treturn { description, globs, alwaysApply };\n}\n\n// ─── Scope Mapping ───────────────────────────────────────────────\n\nfunction determineScope(fields: MdcFields): RuleScope {\n\tif (fields.alwaysApply) return \"always\";\n\tif (fields.globs && fields.globs.length > 0) return \"file-scoped\";\n\tif (fields.description) return \"agent-selected\";\n\treturn \"always\";\n}\n\n// ─── Import Helpers ──────────────────────────────────────────────\n\nfunction importRuleWithoutFrontmatter(\n\traw: string,\n\tfilePath: string,\n\tid: string,\n): { rule: Rule; warning: ImportWarning } {\n\tconst content = raw.replace(/\\r\\n/g, \"\\n\").trim();\n\tconst description = id.replace(/-/g, \" \");\n\treturn {\n\t\trule: {\n\t\t\tid,\n\t\t\tscope: \"always\",\n\t\t\tdescription,\n\t\t\tcontent,\n\t\t\tcategory: \"general\",\n\t\t\tpriority: 3,\n\t\t\testimatedTokens: estimateRuleTokens(content, description),\n\t\t\tsource: {\n\t\t\t\tadapter: \"cursor\",\n\t\t\t\tfilePath,\n\t\t\t\timportedAt: new Date().toISOString(),\n\t\t\t},\n\t\t},\n\t\twarning: {\n\t\t\tfilePath,\n\t\t\tmessage: \"No frontmatter found, treating as always-on rule\",\n\t\t},\n\t};\n}\n\nfunction importRuleWithFrontmatter(\n\tparts: MdcParts,\n\tfilePath: string,\n\tid: string,\n): Rule {\n\tconst fields = parseMdcFields(parts.yaml);\n\tconst scope = determineScope(fields);\n\tconst description = fields.description ?? id.replace(/-/g, \" \");\n\n\treturn {\n\t\tid,\n\t\tscope,\n\t\tdescription,\n\t\tcontent: parts.content,\n\t\tcategory: \"general\",\n\t\tpriority: 3,\n\t\testimatedTokens: estimateRuleTokens(parts.content, description),\n\t\t...(scope === \"file-scoped\" && fields.globs ? { globs: fields.globs } : {}),\n\t\tsource: {\n\t\t\tadapter: \"cursor\",\n\t\t\tfilePath,\n\t\t\timportedAt: new Date().toISOString(),\n\t\t},\n\t};\n}\n\nasync function importMdcFiles(\n\tprojectRoot: string,\n): Promise<{ rules: Rule[]; warnings: ImportWarning[] }> {\n\tconst rules: Rule[] = [];\n\tconst warnings: ImportWarning[] = [];\n\tconst dir = join(projectRoot, RULES_DIR);\n\tconst files = await listMdcFiles(dir);\n\n\tfor (const file of files) {\n\t\tconst filePath = join(RULES_DIR, file);\n\t\tconst raw = await readFile(join(projectRoot, filePath), \"utf-8\");\n\t\tconst id = basename(file, MDC_EXT);\n\t\tconst parts = splitMdcFrontmatter(raw);\n\n\t\tif (!parts) {\n\t\t\tconst result = importRuleWithoutFrontmatter(raw, filePath, id);\n\t\t\trules.push(result.rule);\n\t\t\twarnings.push(result.warning);\n\t\t} else {\n\t\t\trules.push(importRuleWithFrontmatter(parts, filePath, id));\n\t\t}\n\t}\n\n\treturn { rules, warnings };\n}\n\nasync function importLegacyFile(\n\tprojectRoot: string,\n): Promise<{ rules: Rule[]; warnings: ImportWarning[] }> {\n\tconst legacyPath = join(projectRoot, LEGACY_FILE);\n\tif (!(await pathExists(legacyPath))) return { rules: [], warnings: [] };\n\n\tconst raw = await readFile(legacyPath, \"utf-8\");\n\tconst content = raw.trim();\n\tif (content === \"\") return { rules: [], warnings: [] };\n\n\treturn {\n\t\trules: [\n\t\t\t{\n\t\t\t\tid: \"cursorrules-legacy\",\n\t\t\t\tscope: \"always\",\n\t\t\t\tdescription: \"Legacy .cursorrules file\",\n\t\t\t\tcontent,\n\t\t\t\tcategory: \"general\",\n\t\t\t\tpriority: 3,\n\t\t\t\testimatedTokens: estimateRuleTokens(content, \"Legacy .cursorrules\"),\n\t\t\t\tsource: {\n\t\t\t\t\tadapter: \"cursor\",\n\t\t\t\t\tfilePath: LEGACY_FILE,\n\t\t\t\t\timportedAt: new Date().toISOString(),\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\twarnings: [\n\t\t\t{\n\t\t\t\tfilePath: LEGACY_FILE,\n\t\t\t\tmessage: \".cursorrules is deprecated. Migrate to .cursor/rules/*.mdc\",\n\t\t\t},\n\t\t],\n\t};\n}\n\n// ─── Export Helpers ──────────────────────────────────────────────\n\nfunction serializeMdcGlobs(globs: string[]): string {\n\tif (globs.length === 1) {\n\t\tconst first = globs[0];\n\t\tif (first !== undefined) return `globs: \"${first}\"`;\n\t}\n\tconst items = globs.map((g) => `\"${g}\"`).join(\", \");\n\treturn `globs: [${items}]`;\n}\n\nfunction ruleToMdc(rule: Rule): string {\n\tconst lines: string[] = [\"---\"];\n\tlines.push(`description: \"${rule.description}\"`);\n\tif (rule.scope === \"file-scoped\" && rule.globs && rule.globs.length > 0) {\n\t\tlines.push(serializeMdcGlobs(rule.globs));\n\t}\n\tlines.push(`alwaysApply: ${rule.scope === \"always\"}`);\n\tlines.push(\"---\");\n\tlines.push(\"\");\n\tlines.push(rule.content);\n\tlines.push(\"\");\n\treturn lines.join(\"\\n\");\n}\n\nasync function deleteStaleFiles(\n\tprojectRoot: string,\n\texportedIds: Set<string>,\n\tdryRun: boolean,\n): Promise<string[]> {\n\tconst dir = join(projectRoot, RULES_DIR);\n\tconst existing = await listMdcFiles(dir);\n\tconst deleted: string[] = [];\n\n\tfor (const file of existing) {\n\t\tif (!exportedIds.has(file)) {\n\t\t\tconst filePath = join(RULES_DIR, file);\n\t\t\tif (!dryRun) await rm(join(projectRoot, filePath));\n\t\t\tdeleted.push(filePath);\n\t\t}\n\t}\n\n\treturn deleted;\n}\n\n// ─── Adapter ─────────────────────────────────────────────────────\n\nexport const cursorAdapter: RulixAdapter = {\n\tname: \"cursor\",\n\tdisplayName: \"Cursor\",\n\n\tasync detect(projectRoot: string): Promise<boolean> {\n\t\tconst rulesDir = join(projectRoot, RULES_DIR);\n\t\tconst legacyFile = join(projectRoot, LEGACY_FILE);\n\t\treturn (await pathExists(rulesDir)) || (await pathExists(legacyFile));\n\t},\n\n\tasync import(projectRoot: string): Promise<ImportResult> {\n\t\tconst mdc = await importMdcFiles(projectRoot);\n\t\tconst legacy = await importLegacyFile(projectRoot);\n\n\t\treturn {\n\t\t\trules: [...mdc.rules, ...legacy.rules],\n\t\t\twarnings: [...mdc.warnings, ...legacy.warnings],\n\t\t\tsource: RULES_DIR,\n\t\t};\n\t},\n\n\tasync export(\n\t\trules: Rule[],\n\t\tprojectRoot: string,\n\t\toptions?: ExportOptions,\n\t): Promise<ExportResult> {\n\t\tconst dir = join(projectRoot, RULES_DIR);\n\t\tconst dryRun = options?.dryRun === true;\n\t\tconst strategy = options?.strategy ?? \"overwrite\";\n\t\tconst filesWritten: string[] = [];\n\t\tconst warnings: ExportWarning[] = [];\n\n\t\tif (!dryRun) await mkdir(dir, { recursive: true });\n\n\t\tfor (const rule of rules) {\n\t\t\tconst filePath = join(RULES_DIR, `${rule.id}${MDC_EXT}`);\n\t\t\tif (!dryRun) {\n\t\t\t\tawait writeFile(join(projectRoot, filePath), ruleToMdc(rule), \"utf-8\");\n\t\t\t}\n\t\t\tfilesWritten.push(filePath);\n\t\t}\n\n\t\tconst exportedIds = new Set(rules.map((r) => `${r.id}${MDC_EXT}`));\n\t\tconst filesDeleted =\n\t\t\tstrategy === \"overwrite\"\n\t\t\t\t? await deleteStaleFiles(projectRoot, exportedIds, dryRun)\n\t\t\t\t: [];\n\n\t\treturn { filesWritten, filesDeleted, warnings };\n\t},\n\n\tgetTokenBudget(): TokenBudget {\n\t\treturn {\n\t\t\tmaxTokens: 10_000,\n\t\t\tmaxInstructions: 500,\n\t\t\twarningThreshold: 0.8,\n\t\t\tsource: \"Cursor documentation\",\n\t\t};\n\t},\n};\n","/**\n * Adapter registry: lookup by name and auto-detection.\n */\n\nimport type { RulixAdapter } from \"../core/ir.js\";\nimport { agentsMdAdapter } from \"./agents-md.js\";\nimport { claudeCodeAdapter } from \"./claude-code.js\";\nimport { cursorAdapter } from \"./cursor.js\";\n\nconst BUILTIN_ADAPTERS: RulixAdapter[] = [\n\tcursorAdapter,\n\tclaudeCodeAdapter,\n\tagentsMdAdapter,\n];\n\nconst adapterMap = new Map<string, RulixAdapter>(\n\tBUILTIN_ADAPTERS.map((a) => [a.name, a]),\n);\n\n/** Returns all registered adapters. */\nexport function getAdapters(): RulixAdapter[] {\n\treturn [...adapterMap.values()];\n}\n\n/** Returns an adapter by name, or `undefined` if not found. */\nexport function getAdapter(name: string): RulixAdapter | undefined {\n\treturn adapterMap.get(name);\n}\n\n/** Returns all adapter names. */\nexport function getAdapterNames(): string[] {\n\treturn [...adapterMap.keys()];\n}\n\n/** Detects which adapters have existing rules in a project. */\nexport async function detectAdapters(\n\tprojectRoot: string,\n): Promise<RulixAdapter[]> {\n\tconst results = await Promise.all(\n\t\tBUILTIN_ADAPTERS.map(async (adapter) => ({\n\t\t\tadapter,\n\t\t\tdetected: await adapter.detect(projectRoot),\n\t\t})),\n\t);\n\treturn results.filter((r) => r.detected).map((r) => r.adapter);\n}\n","/**\n * Intermediate Representation (IR) types for Rulix.\n *\n * All logic operates on these types, never on raw tool-specific formats.\n * This module defines the core contract between the engine and adapters.\n */\n\n// ─── Scalar Types ────────────────────────────────────────────────\n\nexport type RuleScope = \"always\" | \"file-scoped\" | \"agent-selected\";\n\nexport type RuleCategory =\n\t| \"style\"\n\t| \"security\"\n\t| \"testing\"\n\t| \"architecture\"\n\t| \"workflow\"\n\t| \"general\";\n\nexport type ValidationSeverity = \"error\" | \"warning\" | \"info\";\n\nexport type ExportStrategy = \"overwrite\" | \"merge\";\n\nexport type TokenEstimation = \"heuristic\" | \"tiktoken\";\n\nexport type ClaudeMdStrategy = \"concatenate\" | \"reference\";\n\n// ─── Result Pattern ──────────────────────────────────────────────\n\n/**\n * Use for operations that can fail in predictable ways (parsing, validation).\n * Reserve exceptions for unexpected errors (filesystem, permissions).\n */\nexport type Result<T, E = RulixError> =\n\t| { readonly ok: true; readonly value: T }\n\t| { readonly ok: false; readonly error: E };\n\n/**\n * Every error carries a machine-readable `code` so callers can handle\n * specific failure modes programmatically without parsing message strings.\n */\nexport class RulixError extends Error {\n\tpublic override readonly name = \"RulixError\";\n\n\tconstructor(\n\t\tpublic readonly code: string,\n\t\tmessage: string,\n\t\tpublic override readonly cause?: unknown,\n\t) {\n\t\tsuper(message);\n\t}\n}\n\n// ─── Core IR ─────────────────────────────────────────────────────\n\n/**\n * Tracks where a rule was imported from so users can trace back\n * to the original source file when debugging conflicts.\n */\nexport interface RuleSource {\n\t/** E.g. `\"cursor\"`, `\"claude-code\"`, `\"rulix\"`. */\n\treadonly adapter: string;\n\t/** Relative to the project root. */\n\treadonly filePath: string;\n\t/** ISO 8601 timestamp. */\n\treadonly importedAt: string;\n}\n\n/**\n * The fundamental unit of the Rulix IR. All adapters convert\n * their tool-specific formats to and from this representation.\n */\nexport interface Rule {\n\t/** Kebab-case. Used for deduplication and preset composition. */\n\treadonly id: string;\n\treadonly scope: RuleScope;\n\t/** Used by agent-selected rules to let the AI decide relevance. */\n\treadonly description: string;\n\treadonly content: string;\n\t/** Required when `scope` is `\"file-scoped\"`. */\n\treadonly globs?: string[];\n\treadonly category: RuleCategory;\n\t/** 1 (critical) to 5 (nice-to-have). Drives token budget optimization. */\n\treadonly priority: number;\n\t/** E.g. `\"@rulix/typescript/strict\"`. */\n\treadonly extends?: string;\n\t/** Auto-computed by the tokenizer. */\n\treadonly estimatedTokens: number;\n\treadonly source?: RuleSource;\n}\n\nexport interface RulixConfigOptions {\n\treadonly tokenEstimation: TokenEstimation;\n\t/** Controls the \"Generated by Rulix\" header in AGENTS.md output. */\n\treadonly agentsMdHeader: boolean;\n\treadonly claudeMdStrategy: ClaudeMdStrategy;\n\t/** Used by the `watch` command. */\n\treadonly syncOnSave: boolean;\n}\n\n/**\n * Fully-resolved project configuration with all defaults applied.\n */\nexport interface RulixConfig {\n\t/** Adapter names, e.g. `[\"cursor\", \"claude-code\", \"agents-md\"]`. */\n\treadonly targets: string[];\n\t/** npm packages or local paths to preset rule collections. */\n\treadonly presets: string[];\n\t/** Keyed by rule ID. */\n\treadonly overrides: Record<string, Partial<Rule>>;\n\treadonly options: RulixConfigOptions;\n}\n\nexport interface Ruleset {\n\t/** Local + preset rules, after overrides are applied. */\n\treadonly rules: Rule[];\n\treadonly config: RulixConfig;\n}\n\n// ─── Adapter Interface ───────────────────────────────────────────\n\n/**\n * Each adapter provides its own budget based on the tool's known limits\n * (e.g. Claude Code ~150 instructions, Cursor ~500).\n */\nexport interface TokenBudget {\n\treadonly maxTokens: number;\n\treadonly maxInstructions: number;\n\t/** Fraction of max (e.g. 0.8 for 80%). */\n\treadonly warningThreshold: number;\n\t/** Where the limit comes from (e.g. documentation URL). */\n\treadonly source: string;\n}\n\nexport interface ExportOptions {\n\treadonly strategy: ExportStrategy;\n\t/** Preview changes without writing to disk. */\n\treadonly dryRun?: boolean | undefined;\n}\n\nexport interface ImportWarning {\n\treadonly ruleId?: string | undefined;\n\t/** Source file that produced the warning. */\n\treadonly filePath: string;\n\treadonly message: string;\n}\n\nexport interface ExportWarning {\n\treadonly ruleId?: string | undefined;\n\t/** Target file that produced the warning. */\n\treadonly filePath: string;\n\treadonly message: string;\n}\n\nexport interface ImportResult {\n\treadonly rules: Rule[];\n\treadonly warnings: ImportWarning[];\n\t/** E.g. `\".cursor/rules/\"`. */\n\treadonly source: string;\n}\n\nexport interface ExportResult {\n\treadonly filesWritten: string[];\n\t/** Files removed because the corresponding rule was deleted from source. */\n\treadonly filesDeleted: string[];\n\treadonly warnings: ExportWarning[];\n}\n\n/**\n * Contract that every tool-specific adapter must implement.\n *\n * Import reads tool-specific files into `Rule[]`; export writes `Rule[]`\n * back to the tool's native format.\n */\nexport interface RulixAdapter {\n\t/** E.g. `\"cursor\"`, `\"claude-code\"`. */\n\treadonly name: string;\n\t/** E.g. `\"Cursor\"`, `\"Claude Code\"`. */\n\treadonly displayName: string;\n\n\tdetect(projectRoot: string): Promise<boolean>;\n\timport(projectRoot: string): Promise<ImportResult>;\n\texport(\n\t\trules: Rule[],\n\t\tprojectRoot: string,\n\t\toptions?: ExportOptions,\n\t): Promise<ExportResult>;\n\tgetTokenBudget(): TokenBudget;\n}\n\n// ─── Validation ──────────────────────────────────────────────────\n\n/**\n * Codes follow the pattern `V001`–`V999` as defined in PRD section 11.1.\n */\nexport interface ValidationIssue {\n\t/** E.g. `\"V001\"`. */\n\treadonly code: string;\n\treadonly severity: ValidationSeverity;\n\treadonly message: string;\n\treadonly ruleId?: string | undefined;\n\treadonly filePath?: string | undefined;\n\t/** Actionable fix suggestion. */\n\treadonly suggestion?: string | undefined;\n}\n\nexport interface ValidationResult {\n\t/** `true` when no errors were found (warnings and info are allowed). */\n\treadonly passed: boolean;\n\treadonly errors: ValidationIssue[];\n\treadonly warnings: ValidationIssue[];\n\treadonly info: ValidationIssue[];\n}\n","/**\n * Schema, defaults, and loader for `.rulix/config.json`.\n *\n * Pure validation lives in `resolveConfig`; filesystem I/O lives in `loadConfig`.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type {\n\tClaudeMdStrategy,\n\tResult,\n\tRule,\n\tRulixConfig,\n\tRulixConfigOptions,\n\tTokenEstimation,\n} from \"./ir.js\";\nimport { RulixError } from \"./ir.js\";\n\nexport const RULIX_DIR = \".rulix\";\nexport const CONFIG_FILENAME = \"config.json\";\nexport const RULES_DIR = \"rules\";\n\nconst DEFAULT_OPTIONS: RulixConfigOptions = {\n\ttokenEstimation: \"heuristic\",\n\tagentsMdHeader: true,\n\tclaudeMdStrategy: \"concatenate\",\n\tsyncOnSave: false,\n};\n\nconst DEFAULT_CONFIG: RulixConfig = {\n\ttargets: [],\n\tpresets: [],\n\toverrides: {},\n\toptions: DEFAULT_OPTIONS,\n};\n\nexport function configPath(projectRoot: string): string {\n\treturn join(projectRoot, RULIX_DIR, CONFIG_FILENAME);\n}\n\nexport function rulesPath(projectRoot: string): string {\n\treturn join(projectRoot, RULIX_DIR, RULES_DIR);\n}\n\nexport function createDefaultConfig(): RulixConfig {\n\treturn DEFAULT_CONFIG;\n}\n\n// ─── Validation ──────────────────────────────────────────────────\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n\treturn Array.isArray(value) && value.every((v) => typeof v === \"string\");\n}\n\nfunction configError(message: string): Result<never> {\n\treturn { ok: false, error: new RulixError(\"CONFIG_INVALID\", message) };\n}\n\nfunction resolveOptions(raw: unknown): Result<RulixConfigOptions> {\n\tif (raw === undefined) return { ok: true, value: DEFAULT_OPTIONS };\n\tif (!isRecord(raw)) return configError('\"options\" must be an object');\n\n\tif (\n\t\traw.tokenEstimation !== undefined &&\n\t\traw.tokenEstimation !== \"heuristic\" &&\n\t\traw.tokenEstimation !== \"tiktoken\"\n\t) {\n\t\treturn configError(\n\t\t\t'\"options.tokenEstimation\" must be \"heuristic\" or \"tiktoken\"',\n\t\t);\n\t}\n\tif (\n\t\traw.claudeMdStrategy !== undefined &&\n\t\traw.claudeMdStrategy !== \"concatenate\" &&\n\t\traw.claudeMdStrategy !== \"reference\"\n\t) {\n\t\treturn configError(\n\t\t\t'\"options.claudeMdStrategy\" must be \"concatenate\" or \"reference\"',\n\t\t);\n\t}\n\tif (\n\t\traw.agentsMdHeader !== undefined &&\n\t\ttypeof raw.agentsMdHeader !== \"boolean\"\n\t) {\n\t\treturn configError('\"options.agentsMdHeader\" must be a boolean');\n\t}\n\tif (raw.syncOnSave !== undefined && typeof raw.syncOnSave !== \"boolean\") {\n\t\treturn configError('\"options.syncOnSave\" must be a boolean');\n\t}\n\n\treturn {\n\t\tok: true,\n\t\tvalue: {\n\t\t\ttokenEstimation:\n\t\t\t\t(raw.tokenEstimation as TokenEstimation | undefined) ??\n\t\t\t\tDEFAULT_OPTIONS.tokenEstimation,\n\t\t\tagentsMdHeader:\n\t\t\t\t(raw.agentsMdHeader as boolean | undefined) ??\n\t\t\t\tDEFAULT_OPTIONS.agentsMdHeader,\n\t\t\tclaudeMdStrategy:\n\t\t\t\t(raw.claudeMdStrategy as ClaudeMdStrategy | undefined) ??\n\t\t\t\tDEFAULT_OPTIONS.claudeMdStrategy,\n\t\t\tsyncOnSave:\n\t\t\t\t(raw.syncOnSave as boolean | undefined) ?? DEFAULT_OPTIONS.syncOnSave,\n\t\t},\n\t};\n}\n\n/** Validates raw JSON and merges with defaults to produce a fully-resolved config. */\nexport function resolveConfig(raw: unknown): Result<RulixConfig> {\n\tif (!isRecord(raw)) return configError(\"Config must be a JSON object\");\n\n\tif (raw.targets !== undefined && !isStringArray(raw.targets)) {\n\t\treturn configError('\"targets\" must be an array of strings');\n\t}\n\tif (raw.presets !== undefined && !isStringArray(raw.presets)) {\n\t\treturn configError('\"presets\" must be an array of strings');\n\t}\n\tif (raw.overrides !== undefined && !isRecord(raw.overrides)) {\n\t\treturn configError('\"overrides\" must be an object');\n\t}\n\n\tconst options = resolveOptions(raw.options);\n\tif (!options.ok) return options;\n\n\treturn {\n\t\tok: true,\n\t\tvalue: {\n\t\t\ttargets: (raw.targets as string[] | undefined) ?? DEFAULT_CONFIG.targets,\n\t\t\tpresets: (raw.presets as string[] | undefined) ?? DEFAULT_CONFIG.presets,\n\t\t\toverrides:\n\t\t\t\t(raw.overrides as Record<string, Partial<Rule>> | undefined) ??\n\t\t\t\tDEFAULT_CONFIG.overrides,\n\t\t\toptions: options.value,\n\t\t},\n\t};\n}\n\n// ─── I/O ─────────────────────────────────────────────────────────\n\n/** Reads `.rulix/config.json` from disk. Returns defaults if file is missing. */\nexport async function loadConfig(\n\tprojectRoot: string,\n): Promise<Result<RulixConfig>> {\n\tconst filePath = configPath(projectRoot);\n\n\tlet content: string;\n\ttry {\n\t\tcontent = await readFile(filePath, \"utf-8\");\n\t} catch (error: unknown) {\n\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\treturn { ok: true, value: createDefaultConfig() };\n\t\t}\n\t\tthrow error;\n\t}\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(content);\n\t} catch {\n\t\treturn configError(`Invalid JSON in ${filePath}`);\n\t}\n\n\treturn resolveConfig(raw);\n}\n","/**\n * Hand-rolled frontmatter parser for `.rulix/rules/*.md`.\n *\n * Supports the subset of YAML needed by Rulix frontmatter:\n * simple key-value pairs, quoted strings, numbers, and arrays\n * (both inline `[a, b]` and multi-line `- a`).\n */\n\nimport { mkdir, readdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { rulesPath } from \"./config.js\";\nimport type { Result, Rule, RuleCategory, RuleScope } from \"./ir.js\";\nimport { RulixError } from \"./ir.js\";\nimport { estimateRuleTokens } from \"./tokenizer.js\";\n\nconst DEFAULT_CATEGORY: RuleCategory = \"general\";\nconst DEFAULT_PRIORITY = 3;\n\n// ─── Error Helper ────────────────────────────────────────────────\n\nfunction parseError(message: string, filePath?: string): Result<never> {\n\tconst suffix = filePath ? ` in ${filePath}` : \"\";\n\treturn {\n\t\tok: false,\n\t\terror: new RulixError(\"PARSE_ERROR\", `${message}${suffix}`),\n\t};\n}\n\n// ─── YAML Helpers ────────────────────────────────────────────────\n\nfunction stripQuotes(value: string): string {\n\tconst t = value.trim();\n\tif (\n\t\t(t.startsWith('\"') && t.endsWith('\"')) ||\n\t\t(t.startsWith(\"'\") && t.endsWith(\"'\"))\n\t) {\n\t\treturn t.slice(1, -1);\n\t}\n\treturn t;\n}\n\nfunction parseInlineArray(raw: string): string[] {\n\tconst inner = raw.slice(1, -1).trim();\n\tif (inner === \"\") return [];\n\treturn inner.split(\",\").map(stripQuotes);\n}\n\nfunction parseYamlValue(raw: string): unknown {\n\tconst trimmed = raw.trim();\n\tif (trimmed === \"\") return trimmed;\n\tif (trimmed.startsWith(\"[\") && trimmed.endsWith(\"]\")) {\n\t\treturn parseInlineArray(trimmed);\n\t}\n\tif (/^-?\\d+$/.test(trimmed)) return Number(trimmed);\n\tif (trimmed === \"true\") return true;\n\tif (trimmed === \"false\") return false;\n\treturn stripQuotes(trimmed);\n}\n\n// ─── Frontmatter Splitting ───────────────────────────────────────\n\ninterface FrontmatterParts {\n\treadonly yaml: string;\n\treadonly content: string;\n}\n\nfunction splitFrontmatter(raw: string): Result<FrontmatterParts> {\n\tconst lines = raw.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n\n\tif (lines[0]?.trim() !== \"---\") {\n\t\treturn parseError(\"File must start with frontmatter (---)\");\n\t}\n\n\tlet closingIndex = -1;\n\tfor (let i = 1; i < lines.length; i++) {\n\t\tif (lines[i]?.trim() === \"---\") {\n\t\t\tclosingIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (closingIndex === -1) {\n\t\treturn parseError(\"Unterminated frontmatter (missing closing ---)\");\n\t}\n\n\treturn {\n\t\tok: true,\n\t\tvalue: {\n\t\t\tyaml: lines.slice(1, closingIndex).join(\"\\n\"),\n\t\t\tcontent: lines.slice(closingIndex + 1).join(\"\\n\"),\n\t\t},\n\t};\n}\n\n// ─── Frontmatter Field Parsing ───────────────────────────────────\n\nfunction parseFrontmatterFields(yaml: string): Record<string, unknown> {\n\tconst fields: Record<string, unknown> = {};\n\tconst lines = yaml.split(\"\\n\");\n\tlet arrayKey: string | undefined;\n\tlet arrayItems: string[] = [];\n\n\tfor (const line of lines) {\n\t\tconst trimmed = line.trim();\n\t\tif (trimmed === \"\") continue;\n\n\t\tif (trimmed.startsWith(\"- \")) {\n\t\t\tif (arrayKey !== undefined) {\n\t\t\t\tarrayItems.push(stripQuotes(trimmed.slice(2)));\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (arrayKey !== undefined) {\n\t\t\tfields[arrayKey] = arrayItems;\n\t\t\tarrayKey = undefined;\n\t\t\tarrayItems = [];\n\t\t}\n\n\t\tconst colonIndex = trimmed.indexOf(\":\");\n\t\tif (colonIndex === -1) continue;\n\n\t\tconst key = trimmed.slice(0, colonIndex).trim();\n\t\tconst rawValue = trimmed.slice(colonIndex + 1).trim();\n\n\t\tif (rawValue === \"\") {\n\t\t\tarrayKey = key;\n\t\t\tarrayItems = [];\n\t\t} else {\n\t\t\tfields[key] = parseYamlValue(rawValue);\n\t\t}\n\t}\n\n\tif (arrayKey !== undefined) fields[arrayKey] = arrayItems;\n\n\treturn fields;\n}\n\n// ─── Type Guards ─────────────────────────────────────────────────\n\nfunction isValidScope(value: unknown): value is RuleScope {\n\treturn (\n\t\tvalue === \"always\" || value === \"file-scoped\" || value === \"agent-selected\"\n\t);\n}\n\nfunction isValidCategory(value: unknown): value is RuleCategory {\n\treturn (\n\t\tvalue === \"style\" ||\n\t\tvalue === \"security\" ||\n\t\tvalue === \"testing\" ||\n\t\tvalue === \"architecture\" ||\n\t\tvalue === \"workflow\" ||\n\t\tvalue === \"general\"\n\t);\n}\n\nfunction normalizeGlobs(value: unknown): string[] | undefined {\n\tif (value === undefined) return undefined;\n\tif (typeof value === \"string\") return [value];\n\tif (Array.isArray(value) && value.every((v) => typeof v === \"string\")) {\n\t\treturn value as string[];\n\t}\n\treturn undefined;\n}\n\n// ─── Public API: Parse & Serialize ───────────────────────────────\n\n/** Parses a markdown file with YAML frontmatter into a Rule. */\nexport function parseRule(raw: string, filePath: string): Result<Rule> {\n\tconst split = splitFrontmatter(raw);\n\tif (!split.ok) return parseError(split.error.message, filePath);\n\n\tconst fields = parseFrontmatterFields(split.value.yaml);\n\tconst content = split.value.content.trim();\n\tconst { id, scope, description } = fields;\n\n\tif (typeof id !== \"string\" || id === \"\") {\n\t\treturn parseError('Missing required field \"id\"', filePath);\n\t}\n\tif (!isValidScope(scope)) {\n\t\treturn parseError('Invalid or missing \"scope\"', filePath);\n\t}\n\tif (typeof description !== \"string\" || description === \"\") {\n\t\treturn parseError('Missing required field \"description\"', filePath);\n\t}\n\n\tconst globs = normalizeGlobs(fields.globs);\n\tconst extendsVal =\n\t\ttypeof fields.extends === \"string\" ? fields.extends : undefined;\n\n\treturn {\n\t\tok: true,\n\t\tvalue: {\n\t\t\tid,\n\t\t\tscope,\n\t\t\tdescription,\n\t\t\tcontent,\n\t\t\tcategory: isValidCategory(fields.category)\n\t\t\t\t? fields.category\n\t\t\t\t: DEFAULT_CATEGORY,\n\t\t\tpriority:\n\t\t\t\ttypeof fields.priority === \"number\"\n\t\t\t\t\t? fields.priority\n\t\t\t\t\t: DEFAULT_PRIORITY,\n\t\t\testimatedTokens: estimateRuleTokens(content, description),\n\t\t\t...(globs !== undefined ? { globs } : {}),\n\t\t\t...(extendsVal !== undefined ? { extends: extendsVal } : {}),\n\t\t},\n\t};\n}\n\nfunction serializeGlobs(globs: string[], lines: string[]): void {\n\tif (globs.length === 1) {\n\t\tconst first = globs[0];\n\t\tif (first !== undefined) lines.push(`globs: \"${first}\"`);\n\t\treturn;\n\t}\n\tlines.push(\"globs:\");\n\tfor (const glob of globs) {\n\t\tlines.push(` - \"${glob}\"`);\n\t}\n}\n\n/** Serializes a Rule back to markdown with YAML frontmatter. */\nexport function serializeRule(rule: Rule): string {\n\tconst lines: string[] = [\"---\"];\n\tlines.push(`id: ${rule.id}`);\n\tlines.push(`scope: ${rule.scope}`);\n\tlines.push(`description: \"${rule.description}\"`);\n\tif (rule.globs && rule.globs.length > 0) serializeGlobs(rule.globs, lines);\n\tlines.push(`category: ${rule.category}`);\n\tlines.push(`priority: ${rule.priority}`);\n\tif (rule.extends) lines.push(`extends: ${rule.extends}`);\n\tlines.push(\"---\");\n\tlines.push(\"\");\n\tlines.push(rule.content);\n\tlines.push(\"\");\n\treturn lines.join(\"\\n\");\n}\n\n// ─── Public API: I/O ─────────────────────────────────────────────\n\n/** Reads and parses all `.md` rule files from `.rulix/rules/`. */\nexport async function loadRules(projectRoot: string): Promise<Result<Rule[]>> {\n\tconst dir = rulesPath(projectRoot);\n\n\tlet entries: string[];\n\ttry {\n\t\tentries = await readdir(dir);\n\t} catch (error: unknown) {\n\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\treturn { ok: true, value: [] };\n\t\t}\n\t\tthrow error;\n\t}\n\n\tconst mdFiles = entries.filter((f) => f.endsWith(\".md\")).sort();\n\tconst rules: Rule[] = [];\n\n\tfor (const file of mdFiles) {\n\t\tconst filePath = join(dir, file);\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\tconst result = parseRule(raw, filePath);\n\t\tif (!result.ok) return result;\n\t\trules.push(result.value);\n\t}\n\n\treturn { ok: true, value: rules };\n}\n\n/** Writes a single rule to `.rulix/rules/{id}.md`. Creates the directory if needed. */\nexport async function writeRule(\n\tprojectRoot: string,\n\trule: Rule,\n): Promise<void> {\n\tconst dir = rulesPath(projectRoot);\n\tawait mkdir(dir, { recursive: true });\n\tawait writeFile(join(dir, `${rule.id}.md`), serializeRule(rule), \"utf-8\");\n}\n","/**\n * Validation engine for Rulix rules (V001–V010).\n *\n * Each check is a pure function that inspects rules structurally.\n * V006 (token budgets) and V008 (agent-selected support) are deferred\n * to the adapter layer where tool-specific limits are known.\n */\n\nimport type {\n\tRule,\n\tValidationIssue,\n\tValidationResult,\n\tValidationSeverity,\n} from \"./ir.js\";\n\nconst MIN_CONTENT_LENGTH = 20;\nconst MAX_CONTENT_LINES = 50;\n\nconst VAGUE_PATTERNS: RegExp[] = [\n\t/handle .+ properly/i,\n\t/do .+ correctly/i,\n\t/implement .+ properly/i,\n\t/make sure .+ works/i,\n\t/ensure .+ is correct/i,\n\t/follow best practices/i,\n];\n\n// ─── Issue Factory ───────────────────────────────────────────────\n\nfunction createIssue(\n\tcode: string,\n\tseverity: ValidationSeverity,\n\tmessage: string,\n\truleId?: string,\n\tsuggestion?: string,\n): ValidationIssue {\n\treturn {\n\t\tcode,\n\t\tseverity,\n\t\tmessage,\n\t\t...(ruleId !== undefined ? { ruleId } : {}),\n\t\t...(suggestion !== undefined ? { suggestion } : {}),\n\t};\n}\n\n// ─── Cross-Rule Checks ──────────────────────────────────────────\n\nfunction checkDuplicateIds(rules: Rule[]): ValidationIssue[] {\n\tconst seen = new Map<string, number>();\n\tconst issues: ValidationIssue[] = [];\n\n\tfor (const rule of rules) {\n\t\tconst count = (seen.get(rule.id) ?? 0) + 1;\n\t\tseen.set(rule.id, count);\n\t\tif (count === 2) {\n\t\t\tissues.push(\n\t\t\t\tcreateIssue(\n\t\t\t\t\t\"V001\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\t`Duplicate rule ID \"${rule.id}\"`,\n\t\t\t\t\trule.id,\n\t\t\t\t\t\"Rename one of the duplicate rules\",\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\n\treturn issues;\n}\n\n// ─── Per-Rule Checks ─────────────────────────────────────────────\n\nfunction checkRequiredFields(rule: Rule): ValidationIssue[] {\n\tconst issues: ValidationIssue[] = [];\n\tif (rule.id === \"\") {\n\t\tissues.push(createIssue(\"V002\", \"error\", \"Rule has empty ID\", rule.id));\n\t}\n\tif (rule.description === \"\") {\n\t\tissues.push(\n\t\t\tcreateIssue(\n\t\t\t\t\"V002\",\n\t\t\t\t\"error\",\n\t\t\t\t`Rule \"${rule.id}\" has empty description`,\n\t\t\t\trule.id,\n\t\t\t),\n\t\t);\n\t}\n\treturn issues;\n}\n\nfunction checkFileScopedGlobs(rule: Rule): ValidationIssue[] {\n\tif (rule.scope !== \"file-scoped\") return [];\n\tif (!rule.globs || rule.globs.length === 0) {\n\t\treturn [\n\t\t\tcreateIssue(\n\t\t\t\t\"V003\",\n\t\t\t\t\"error\",\n\t\t\t\t`Rule \"${rule.id}\" is file-scoped but has no globs`,\n\t\t\t\trule.id,\n\t\t\t\t'Add globs or change scope to \"always\"',\n\t\t\t),\n\t\t];\n\t}\n\treturn [];\n}\n\nfunction checkShortContent(rule: Rule): ValidationIssue[] {\n\tif (rule.content.length < MIN_CONTENT_LENGTH) {\n\t\treturn [\n\t\t\tcreateIssue(\n\t\t\t\t\"V004\",\n\t\t\t\t\"warning\",\n\t\t\t\t`Rule \"${rule.id}\" has very short content (${rule.content.length} chars)`,\n\t\t\t\trule.id,\n\t\t\t\t\"Consider adding more detail\",\n\t\t\t),\n\t\t];\n\t}\n\treturn [];\n}\n\nfunction checkVagueDescription(rule: Rule): ValidationIssue[] {\n\tfor (const pattern of VAGUE_PATTERNS) {\n\t\tif (pattern.test(rule.description)) {\n\t\t\treturn [\n\t\t\t\tcreateIssue(\n\t\t\t\t\t\"V005\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t\t`Rule \"${rule.id}\" has a vague description`,\n\t\t\t\t\trule.id,\n\t\t\t\t\t\"Be more specific about what conventions to follow\",\n\t\t\t\t),\n\t\t\t];\n\t\t}\n\t}\n\treturn [];\n}\n\nfunction checkDefaultCategory(rule: Rule): ValidationIssue[] {\n\tif (rule.category === \"general\") {\n\t\treturn [\n\t\t\tcreateIssue(\n\t\t\t\t\"V007\",\n\t\t\t\t\"info\",\n\t\t\t\t`Rule \"${rule.id}\" has no specific category`,\n\t\t\t\trule.id,\n\t\t\t\t\"Consider assigning a category (style, security, testing, architecture, workflow)\",\n\t\t\t),\n\t\t];\n\t}\n\treturn [];\n}\n\nfunction isValidGlobSyntax(pattern: string): boolean {\n\tif (pattern.trim() === \"\") return false;\n\tlet brackets = 0;\n\tlet braces = 0;\n\tfor (const ch of pattern) {\n\t\tif (ch === \"[\") brackets++;\n\t\telse if (ch === \"]\") brackets--;\n\t\telse if (ch === \"{\") braces++;\n\t\telse if (ch === \"}\") braces--;\n\t\tif (brackets < 0 || braces < 0) return false;\n\t}\n\treturn brackets === 0 && braces === 0;\n}\n\nfunction checkGlobSyntax(rule: Rule): ValidationIssue[] {\n\tif (!rule.globs) return [];\n\tconst issues: ValidationIssue[] = [];\n\tfor (const glob of rule.globs) {\n\t\tif (!isValidGlobSyntax(glob)) {\n\t\t\tissues.push(\n\t\t\t\tcreateIssue(\n\t\t\t\t\t\"V009\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\t`Rule \"${rule.id}\" has invalid glob pattern: \"${glob}\"`,\n\t\t\t\t\trule.id,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn issues;\n}\n\nfunction checkLongContent(rule: Rule): ValidationIssue[] {\n\tconst lineCount = rule.content.split(\"\\n\").length;\n\tif (lineCount > MAX_CONTENT_LINES) {\n\t\treturn [\n\t\t\tcreateIssue(\n\t\t\t\t\"V010\",\n\t\t\t\t\"warning\",\n\t\t\t\t`Rule \"${rule.id}\" has ${lineCount} lines (>${MAX_CONTENT_LINES})`,\n\t\t\t\trule.id,\n\t\t\t\t\"Consider splitting into smaller rules\",\n\t\t\t),\n\t\t];\n\t}\n\treturn [];\n}\n\n// ─── Result Builder ──────────────────────────────────────────────\n\nfunction buildResult(issues: ValidationIssue[]): ValidationResult {\n\treturn {\n\t\tpassed: issues.every((i) => i.severity !== \"error\"),\n\t\terrors: issues.filter((i) => i.severity === \"error\"),\n\t\twarnings: issues.filter((i) => i.severity === \"warning\"),\n\t\tinfo: issues.filter((i) => i.severity === \"info\"),\n\t};\n}\n\n// ─── Public API ──────────────────────────────────────────────────\n\n/** Validates rules for structural issues (V001–V005, V007, V009, V010). */\nexport function validateRules(rules: Rule[]): ValidationResult {\n\tconst issues: ValidationIssue[] = [];\n\n\tissues.push(...checkDuplicateIds(rules));\n\n\tfor (const rule of rules) {\n\t\tissues.push(...checkRequiredFields(rule));\n\t\tissues.push(...checkFileScopedGlobs(rule));\n\t\tissues.push(...checkShortContent(rule));\n\t\tissues.push(...checkVagueDescription(rule));\n\t\tissues.push(...checkDefaultCategory(rule));\n\t\tissues.push(...checkGlobSyntax(rule));\n\t\tissues.push(...checkLongContent(rule));\n\t}\n\n\treturn buildResult(issues);\n}\n"]}