@ez-corp/ez-context 0.1.12 → 0.1.14

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.
package/README.md CHANGED
@@ -161,7 +161,7 @@ Contributions are welcome. Please open an issue to discuss significant changes b
161
161
 
162
162
  ```bash
163
163
  # Clone and install
164
- git clone https://github.com/ez-corp/ez-context.git
164
+ git clone https://github.com/ezcorp-org/ez-context.git
165
165
  cd ez-context
166
166
  npm install
167
167
 
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as writeWithMarkers, i as MARKER_START, n as emit, o as extractConventions, r as MARKER_END, s as createBridge, t as FORMAT_EMITTER_MAP } from "./emitters-DOtul0uF.js";
2
+ import { d as MARKER_START, f as writeWithMarkers, m as createBridge, n as emit, p as extractConventions, t as FORMAT_EMITTER_MAP, u as MARKER_END } from "./emitters-Csij2cRu.js";
3
3
  import { copyFile, readFile, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { existsSync } from "node:fs";
@@ -362,6 +362,7 @@ async function driftAction(pathArg, options) {
362
362
  spinner.fail("Drift analysis failed");
363
363
  const message = err instanceof Error ? err.message : String(err);
364
364
  console.error(chalk.red(message));
365
+ if (message.includes("index") || message.includes("embedding") || message.includes("vector")) console.error(chalk.yellow("Hint: try deleting .ez-search/ and running again."));
365
366
  process.exit(1);
366
367
  }
367
368
  }
@@ -580,13 +581,14 @@ async function updateAction(pathArg, options) {
580
581
  spinner.fail("Update failed");
581
582
  const message = err instanceof Error ? err.message : String(err);
582
583
  console.error(chalk.red(message));
584
+ if (message.includes("index") || message.includes("embedding") || message.includes("vector")) console.error(chalk.yellow("Hint: try deleting .ez-search/ and running again."));
583
585
  process.exit(1);
584
586
  }
585
587
  }
586
588
 
587
589
  //#endregion
588
590
  //#region package.json
589
- var version = "0.1.11";
591
+ var version = "0.1.14";
590
592
 
591
593
  //#endregion
592
594
  //#region src/cli.ts
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":["pkg.version"],"sources":["../src/commands/generate.ts","../src/commands/inspect.ts","../src/core/drift/claim-extractor.ts","../src/core/drift/claim-scorer.ts","../src/core/drift/report.ts","../src/commands/drift.ts","../src/core/updater.ts","../src/commands/update.ts","../package.json","../src/cli.ts"],"sourcesContent":["import path from \"node:path\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { extractConventions } from \"../core/pipeline.js\";\nimport { emit } from \"../emitters/index.js\";\nimport type { EmitOptions, OutputFormat } from \"../emitters/types.js\";\n\nconst DRY_RUN_PREVIEW_LINES = 20;\n\nconst VALID_FORMATS: OutputFormat[] = [\n \"claude\",\n \"agents\",\n \"cursor\",\n \"copilot\",\n \"skills\",\n \"rulesync\",\n \"ruler\",\n];\n\nexport function parseFormats(raw: string): OutputFormat[] {\n const formats = [...new Set(raw.split(\",\").map((s) => s.trim()).filter(Boolean))];\n const invalid = formats.filter((f) => !VALID_FORMATS.includes(f as OutputFormat));\n if (invalid.length > 0) {\n throw new Error(\n `Invalid format(s): ${invalid.join(\", \")}. Valid: ${VALID_FORMATS.join(\", \")}`\n );\n }\n return formats as OutputFormat[];\n}\n\nfunction truncatePreview(content: string): string {\n const lines = content.split(\"\\n\");\n if (lines.length <= DRY_RUN_PREVIEW_LINES) {\n return content;\n }\n const preview = lines.slice(0, DRY_RUN_PREVIEW_LINES).join(\"\\n\");\n return `${preview}\\n... (${lines.length} lines total)`;\n}\n\nexport async function generateAction(\n pathArg: string,\n options: { dryRun?: boolean; yes?: boolean; output?: string; threshold?: string; format?: string }\n): Promise<void> {\n const projectPath = path.resolve(pathArg);\n\n // --yes and non-TTY environments: ora handles non-TTY gracefully by\n // falling back to plain text. No interactive prompts are used anywhere.\n const spinner = ora(\"Analyzing project conventions...\").start();\n\n try {\n const registry = await extractConventions(projectPath);\n const conventionCount = registry.conventions.length;\n spinner.succeed(`Found ${conventionCount} convention${conventionCount === 1 ? \"\" : \"s\"}`);\n\n const confidenceThreshold = parseFloat(options.threshold ?? \"0.7\");\n if (Number.isNaN(confidenceThreshold) || confidenceThreshold < 0 || confidenceThreshold > 1) {\n console.error(chalk.red(\"Invalid --threshold: must be a number between 0 and 1\"));\n process.exit(1);\n }\n const outputDir = path.resolve(options.output ?? \".\");\n\n // Parse and validate --format (default: \"claude,agents\")\n const formats = parseFormats(options.format ?? \"claude,agents\");\n\n const emitOptions: EmitOptions = {\n outputDir,\n confidenceThreshold,\n dryRun: options.dryRun ?? false,\n formats,\n };\n\n const isDefault = formats.length === 2 && formats.includes(\"claude\") && formats.includes(\"agents\");\n const genSpinnerText = isDefault\n ? \"Generating context files...\"\n : `Generating ${formats.length} context file${formats.length === 1 ? \"\" : \"s\"}...`;\n const genSpinner = ora(genSpinnerText).start();\n const result = await emit(registry, emitOptions);\n\n if (options.dryRun) {\n genSpinner.succeed(\"Dry run complete\");\n console.log();\n console.log(chalk.bold.yellow(\"╔══════════════════════════════════════╗\"));\n console.log(chalk.bold.yellow(\"║ DRY RUN -- no files will be written ║\"));\n console.log(chalk.bold.yellow(\"╚══════════════════════════════════════╝\"));\n console.log();\n for (const [format, content] of Object.entries(result.rendered)) {\n console.log(chalk.cyan(`--- ${format.toUpperCase()} ---`));\n console.log(truncatePreview(content));\n console.log();\n }\n } else {\n genSpinner.succeed(`Generated ${result.filesWritten.length} file${result.filesWritten.length === 1 ? \"\" : \"s\"}`);\n console.log();\n console.log(chalk.bold.green(\"Generated files:\"));\n for (const filePath of result.filesWritten) {\n const relPath = path.relative(outputDir, filePath);\n console.log(` ${chalk.cyan(relPath)}`);\n }\n }\n } catch (err) {\n spinner.fail(\"Analysis failed\");\n const message = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(message));\n process.exit(1);\n }\n}\n","import path from \"node:path\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { extractConventions } from \"../core/pipeline.js\";\nimport type { ConventionEntry } from \"../core/schema.js\";\n\nfunction confidenceDot(confidence: number): string {\n if (confidence >= 0.8) return chalk.green(\"●\");\n if (confidence >= 0.6) return chalk.yellow(\"●\");\n return chalk.red(\"●\");\n}\n\nexport async function inspectAction(\n pathArg: string,\n options: { threshold?: string }\n): Promise<void> {\n const projectPath = path.resolve(pathArg);\n const spinner = ora(\"Analyzing project conventions...\").start();\n\n try {\n const registry = await extractConventions(projectPath);\n const totalCount = registry.conventions.length;\n spinner.succeed(`Extracted ${totalCount} convention${totalCount === 1 ? \"\" : \"s\"}`);\n\n const threshold = parseFloat(options.threshold ?? \"0.7\");\n if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {\n console.error(chalk.red(\"Invalid --threshold: must be a number between 0 and 1\"));\n process.exit(1);\n }\n\n const filtered = registry.conventions.filter(\n (c) => c.confidence >= threshold\n );\n\n if (filtered.length === 0) {\n console.log(\n chalk.yellow(\n `\\nNo conventions found above ${threshold} confidence threshold. Try lowering --threshold.`\n )\n );\n return;\n }\n\n // Group by category\n const byCategory = new Map<string, ConventionEntry[]>();\n for (const convention of filtered) {\n const group = byCategory.get(convention.category) ?? [];\n group.push(convention);\n byCategory.set(convention.category, group);\n }\n\n console.log();\n for (const [category, conventions] of byCategory) {\n console.log(chalk.bold(category.toUpperCase()));\n for (const convention of conventions) {\n const pct = Math.round(convention.confidence * 100);\n console.log(\n ` ${confidenceDot(convention.confidence)} ${convention.pattern} ${chalk.gray(`(${pct}%)`)}`\n );\n }\n console.log();\n }\n\n const categoryCount = byCategory.size;\n console.log(\n chalk.gray(\n `Found ${filtered.length} convention${filtered.length === 1 ? \"\" : \"s\"} across ${categoryCount} categor${categoryCount === 1 ? \"y\" : \"ies\"} (threshold: ${threshold})`\n )\n );\n } catch (err) {\n spinner.fail(\"Analysis failed\");\n const message = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(message));\n process.exit(1);\n }\n}\n","/**\n * Claim extractor — parses markdown context files into individual testable claims.\n *\n * Input: raw markdown string (CLAUDE.md, AGENTS.md, .cursorrules, etc.)\n * Output: Claim[] where each claim is an atomic declarative statement\n *\n * Extraction rules:\n * - Bullet points: ^[-*+]\\s+\n * - Numbered list items: ^\\d+\\.\\s+\n * - Bold/code markers stripped from extracted text\n * - Boilerplate value lines skipped (Language: X, Framework: X, etc.)\n * - ez-context markers and HTML comments skipped\n * - Claims shorter than 10 chars or longer than 300 chars excluded\n * - Current section heading tracked for context\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface Claim {\n text: string; // The claim text (bold/code markers stripped)\n sourceFile: string; // Which file it came from\n sourceLine: number; // 1-based line number\n sourceSection: string; // Nearest parent heading\n}\n\n// ---------------------------------------------------------------------------\n// Filters\n// ---------------------------------------------------------------------------\n\n/**\n * Matches boilerplate key-value lines that are structural metadata, not\n * behavioral claims. Applied AFTER bold/code stripping.\n *\n * Examples skipped:\n * \"Language: TypeScript\"\n * \"Package Manager: bun\"\n */\nconst BOILERPLATE_VALUE =\n /^(Language|Framework|Build|Package Manager|Test Runner|Pattern|Layers):\\s/i;\n\n// ---------------------------------------------------------------------------\n// Core function\n// ---------------------------------------------------------------------------\n\n/**\n * Extract all testable claims from a markdown string.\n *\n * @param content Raw markdown content of the context file\n * @param sourceFile Path to the source file (stored on each claim)\n * @returns Array of extracted claims, filtered and deduplicated\n */\nexport function extractClaims(content: string, sourceFile: string): Claim[] {\n const claims: Claim[] = [];\n const lines = content.split(\"\\n\");\n let currentSection = \"\";\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!.trim();\n const lineNum = i + 1; // 1-based\n\n // Skip blank lines\n if (!line) continue;\n\n // Skip HTML comments (includes ez-context markers like <!-- ez-context:... -->)\n if (line.startsWith(\"<!--\")) continue;\n\n // Skip lines containing ez-context markers (belt-and-suspenders for inline markers)\n if (line.includes(\"ez-context:\")) continue;\n\n // Track section headings — H1, H2, H3\n const heading = line.match(/^#{1,3}\\s+(.+)/);\n if (heading) {\n currentSection = heading[1]!.trim();\n continue;\n }\n\n // Match bullet points or numbered list items\n const bullet = line.match(/^[-*+]\\s+(.+)/);\n const numbered = !bullet ? line.match(/^\\d+\\.\\s+(.+)/) : null;\n const rawText = bullet ? bullet[1]! : numbered ? numbered[1]! : null;\n\n if (!rawText) continue;\n\n // Strip bold markers (**text** -> text) and inline code markers (`text` -> text)\n const text = rawText\n .replace(/\\*\\*([^*]+)\\*\\*/g, \"$1\")\n .replace(/`([^`]+)`/g, \"$1\")\n .trim();\n\n // Apply length filters\n if (text.length < 10 || text.length > 300) continue;\n\n // Skip boilerplate key-value lines\n if (BOILERPLATE_VALUE.test(text)) continue;\n\n claims.push({\n text,\n sourceFile,\n sourceLine: lineNum,\n sourceSection: currentSection,\n });\n }\n\n return claims;\n}\n","/**\n * Claim scorer — compares extracted claims against the code index via semantic search.\n *\n * Each claim is searched against the indexed codebase. The top similarity score\n * determines whether the claim is GREEN (well-supported), YELLOW (possibly stale),\n * or RED (contradicted / not found).\n *\n * Claims are processed in batches to avoid ONNX pipeline contention.\n */\nimport type { Claim } from \"./claim-extractor.js\";\nimport type { SearchResult, EzSearchBridge } from \"../ez-search-bridge.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nexport const GREEN_THRESHOLD = 0.65;\nexport const YELLOW_THRESHOLD = 0.40;\nexport const BATCH_SIZE = 10;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ClaimStatus = \"GREEN\" | \"YELLOW\" | \"RED\";\n\nexport interface ScoredClaim {\n claim: Claim;\n status: ClaimStatus;\n score: number; // Top bridge.search() score (0.0-1.0)\n evidence: SearchResult[]; // Top k results\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction chunk<T>(arr: T[], size: number): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < arr.length; i += size) {\n chunks.push(arr.slice(i, i + size));\n }\n return chunks;\n}\n\nfunction classifyScore(score: number): ClaimStatus {\n if (score >= GREEN_THRESHOLD) return \"GREEN\";\n if (score >= YELLOW_THRESHOLD) return \"YELLOW\";\n return \"RED\";\n}\n\nasync function scoreSingleClaim(\n claim: Claim,\n bridge: EzSearchBridge\n): Promise<ScoredClaim> {\n const evidence = await bridge.search(claim.text, { k: 5 });\n const topScore = evidence.length > 0 ? evidence[0]!.score : 0;\n return {\n claim,\n status: classifyScore(topScore),\n score: topScore,\n evidence,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Score all claims by searching the code index in batches of BATCH_SIZE.\n *\n * @param claims Claims to score\n * @param bridge EzSearchBridge instance bound to the project\n * @param onProgress Optional callback fired after each batch: (done, total)\n * @returns ScoredClaim[] in the same order as input claims\n */\nexport async function scoreClaims(\n claims: Claim[],\n bridge: EzSearchBridge,\n onProgress?: (completed: number, total: number) => void\n): Promise<ScoredClaim[]> {\n const total = claims.length;\n const batches = chunk(claims, BATCH_SIZE);\n const results: ScoredClaim[] = [];\n let completed = 0;\n\n for (const batch of batches) {\n const batchResults = await Promise.all(\n batch.map((claim) => scoreSingleClaim(claim, bridge))\n );\n results.push(...batchResults);\n completed += batch.length;\n onProgress?.(completed, total);\n }\n\n return results;\n}\n","/**\n * Drift report — aggregates scored claims into a health score and markdown report.\n *\n * Health score: mean of per-claim scores scaled 0-100 (rounded).\n * Zero claims yields a health score of 100 (nothing to contradict = healthy).\n *\n * Rendered markdown groups claims by status with evidence for stale/contradicted claims.\n */\nimport type { ScoredClaim } from \"./claim-scorer.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DriftReport {\n sourceFile: string;\n healthScore: number;\n scoredClaims: ScoredClaim[];\n}\n\n// ---------------------------------------------------------------------------\n// Health score\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the aggregate health score for a set of scored claims.\n * Returns 100 for empty input (no claims = no drift).\n */\nexport function computeHealthScore(scoredClaims: ScoredClaim[]): number {\n if (scoredClaims.length === 0) return 100;\n const mean =\n scoredClaims.reduce((sum, sc) => sum + sc.score, 0) / scoredClaims.length;\n return Math.round(mean * 100);\n}\n\n// ---------------------------------------------------------------------------\n// Report assembly\n// ---------------------------------------------------------------------------\n\n/**\n * Build a DriftReport from a source file path and its scored claims.\n */\nexport function buildDriftReport(\n sourceFile: string,\n scoredClaims: ScoredClaim[]\n): DriftReport {\n return {\n sourceFile,\n healthScore: computeHealthScore(scoredClaims),\n scoredClaims,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Renderer\n// ---------------------------------------------------------------------------\n\nconst STATUS_LABEL: Record<string, string> = {\n GREEN: \"Confirmed\",\n YELLOW: \"Possibly Stale\",\n RED: \"Contradicted\",\n};\n\n/**\n * Render a drift report as a readable markdown string.\n *\n * Layout:\n * # Drift Report\n * Health score, source file, claim count\n *\n * ## Confirmed (GREEN)\n * - [GREEN] claim text (score: X.XX)\n *\n * ## Possibly Stale (YELLOW)\n * - [YELLOW] claim text (score: X.XX)\n * - file: chunk_preview\n *\n * ## Contradicted (RED)\n * - [RED] claim text (score: X.XX)\n * - file: chunk_preview\n *\n * Summary: X confirmed, Y possibly stale, Z contradicted\n */\nexport function renderDriftReport(report: DriftReport): string {\n const { sourceFile, healthScore, scoredClaims } = report;\n const lines: string[] = [];\n\n const green = scoredClaims.filter((sc) => sc.status === \"GREEN\");\n const yellow = scoredClaims.filter((sc) => sc.status === \"YELLOW\");\n const red = scoredClaims.filter((sc) => sc.status === \"RED\");\n\n // Header\n lines.push(\"# Drift Report\");\n lines.push(\"\");\n lines.push(`**Health Score:** ${healthScore}/100`);\n lines.push(`**File:** ${sourceFile}`);\n lines.push(`**Claims:** ${scoredClaims.length}`);\n lines.push(\"\");\n\n // Render a group of claims\n const renderGroup = (group: ScoredClaim[], status: string) => {\n if (group.length === 0) return;\n const label = STATUS_LABEL[status] ?? status;\n lines.push(`## ${label} (${status})`);\n lines.push(\"\");\n for (const sc of group) {\n lines.push(`- [${sc.status}] ${sc.claim.text} (score: ${sc.score.toFixed(2)})`);\n // Show top 2 evidence items for non-GREEN claims\n if (sc.status !== \"GREEN\") {\n const topEvidence = sc.evidence.slice(0, 2);\n for (const ev of topEvidence) {\n const preview = ev.chunk.replace(/\\s+/g, \" \").trim().slice(0, 80);\n lines.push(` - ${ev.file}: ${preview}`);\n }\n }\n }\n lines.push(\"\");\n };\n\n renderGroup(green, \"GREEN\");\n renderGroup(yellow, \"YELLOW\");\n renderGroup(red, \"RED\");\n\n // Summary\n lines.push(\n `Summary: ${green.length} confirmed, ${yellow.length} possibly stale, ${red.length} contradicted`\n );\n\n return lines.join(\"\\n\");\n}\n","import path from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { createBridge } from \"../core/ez-search-bridge.js\";\nimport { extractClaims } from \"../core/drift/claim-extractor.js\";\nimport { scoreClaims } from \"../core/drift/claim-scorer.js\";\nimport { buildDriftReport, renderDriftReport, computeHealthScore } from \"../core/drift/report.js\";\n\nconst CANDIDATE_FILES = [\"CLAUDE.md\", \"AGENTS.md\", \".cursorrules\", \"CONTEXT.md\"];\n\nfunction healthColor(score: number): string {\n if (score >= 70) return chalk.green(String(score));\n if (score >= 40) return chalk.yellow(String(score));\n return chalk.red(String(score));\n}\n\nexport async function driftAction(\n pathArg: string,\n options: { file?: string }\n): Promise<void> {\n const projectPath = path.resolve(pathArg);\n const spinner = ora(\"Loading context files...\").start();\n\n try {\n const bridge = await createBridge(projectPath);\n\n // Always refresh so search results reflect current file state\n spinner.text = \"Refreshing search index...\";\n await bridge.refreshIndex(projectPath);\n spinner.text = \"Loading context files...\";\n\n // Resolve files\n let filePaths: string[];\n if (options.file) {\n filePaths = [path.resolve(projectPath, options.file)];\n } else {\n filePaths = CANDIDATE_FILES\n .map((name) => path.join(projectPath, name))\n .filter((p) => existsSync(p));\n }\n\n if (filePaths.length === 0) {\n spinner.fail(\"No context files found\");\n console.error(\n chalk.red(\"No CLAUDE.md, AGENTS.md, .cursorrules, or CONTEXT.md found. Use --file to specify one.\")\n );\n process.exit(1);\n }\n\n // Extract claims from each file\n const claimsByFile: Map<string, ReturnType<typeof extractClaims>> = new Map();\n for (const filePath of filePaths) {\n const content = await readFile(filePath, \"utf-8\");\n const claims = extractClaims(content, filePath);\n claimsByFile.set(filePath, claims);\n }\n\n const allClaims = [...claimsByFile.values()].flat();\n spinner.text = `Analyzing ${allClaims.length} claims...`;\n\n // Score claims with progress callback\n const scoredAll = await scoreClaims(allClaims, bridge, (done, total) => {\n spinner.text = `Checking claim ${done}/${total}...`;\n });\n\n // Build and render reports per file\n const reports = filePaths.map((filePath) => {\n const fileClaims = claimsByFile.get(filePath) ?? [];\n const fileScoredClaims = scoredAll.filter((sc) =>\n fileClaims.some((c) => c === sc.claim)\n );\n return buildDriftReport(filePath, fileScoredClaims);\n });\n\n const overallScore = computeHealthScore(scoredAll);\n spinner.succeed(`Drift analysis complete — health score: ${healthColor(overallScore)}/100`);\n\n console.log();\n for (const report of reports) {\n console.log(renderDriftReport(report));\n console.log();\n }\n } catch (err) {\n spinner.fail(\"Drift analysis failed\");\n const message = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(message));\n process.exit(1);\n }\n}\n","/**\n * Updater — targeted regeneration engine for `ez-context update`.\n *\n * Orchestrates:\n * 1. Marker validation (pre-flight check, markers strategy only)\n * 2. Drift detection (skip GREEN files, markers strategy only)\n * 3. File backup (before any write)\n * 4. Re-rendering (via FORMAT_EMITTER_MAP)\n * 5. Write-back (writeWithMarkers for markers strategy, writeFile for direct)\n */\nimport { copyFile, readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { MARKER_START, MARKER_END, writeWithMarkers } from \"../emitters/writer.js\";\nimport { FORMAT_EMITTER_MAP } from \"../emitters/index.js\";\nimport { extractClaims } from \"./drift/claim-extractor.js\";\nimport { scoreClaims } from \"./drift/claim-scorer.js\";\nimport type { EzSearchBridge } from \"./ez-search-bridge.js\";\nimport type { ConventionRegistry } from \"./schema.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MarkerValidation {\n valid: boolean;\n mode: \"append\" | \"splice\" | \"invalid\";\n reason?: string;\n startIdx?: number;\n endIdx?: number;\n}\n\nexport type UpdateAction = \"skipped\" | \"updated\" | \"aborted\";\n\nexport interface FileUpdateResult {\n filePath: string;\n action: UpdateAction;\n reason: string;\n backupPath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// validateMarkers\n// ---------------------------------------------------------------------------\n\n/**\n * Pre-flight marker check for updateFile.\n *\n * Unlike writeWithMarkers (which silently appends on unpaired markers),\n * validateMarkers rejects unpaired markers so updateFile can abort safely.\n *\n * Returns:\n * - { valid: true, mode: \"append\" } — no markers, safe to append\n * - { valid: true, mode: \"splice\", startIdx, endIdx } — well-formed pair\n * - { valid: false, mode: \"invalid\", reason } — unpaired or inverted markers\n */\nexport function validateMarkers(content: string): MarkerValidation {\n const startIdx = content.indexOf(MARKER_START);\n const endIdx = content.indexOf(MARKER_END);\n\n const hasStart = startIdx !== -1;\n const hasEnd = endIdx !== -1;\n\n // No markers at all -> safe to append\n if (!hasStart && !hasEnd) {\n return { valid: true, mode: \"append\" };\n }\n\n // Both markers present -> validate ordering\n if (hasStart && hasEnd) {\n if (endIdx < startIdx) {\n return {\n valid: false,\n mode: \"invalid\",\n reason: \"End marker appears before start marker (corrupted file)\",\n };\n }\n return { valid: true, mode: \"splice\", startIdx, endIdx };\n }\n\n // Unpaired: only one marker present\n if (hasStart && !hasEnd) {\n return {\n valid: false,\n mode: \"invalid\",\n reason: \"Unpaired ez-context marker: end marker missing\",\n };\n }\n\n // hasEnd && !hasStart\n return {\n valid: false,\n mode: \"invalid\",\n reason: \"Unpaired ez-context marker: start marker missing\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// backupFile\n// ---------------------------------------------------------------------------\n\n/**\n * Copy filePath to filePath.bak and return the backup path.\n * Returns null if the file does not exist.\n * Overwrites any existing .bak silently (represents state before this run).\n */\nexport async function backupFile(filePath: string): Promise<string | null> {\n if (!existsSync(filePath)) {\n return null;\n }\n\n const backupPath = filePath + \".bak\";\n await copyFile(filePath, backupPath);\n return backupPath;\n}\n\n// ---------------------------------------------------------------------------\n// findFormatEntry\n// ---------------------------------------------------------------------------\n\n/**\n * Look up the FORMAT_EMITTER_MAP entry whose filename suffix matches filePath.\n * Returns undefined if the file doesn't correspond to a known format.\n */\nfunction findFormatEntry(filePath: string) {\n const normalized = path.normalize(filePath);\n for (const entry of Object.values(FORMAT_EMITTER_MAP)) {\n if (normalized.endsWith(path.normalize(entry.filename))) {\n return entry;\n }\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// updateFile\n// ---------------------------------------------------------------------------\n\n/**\n * Orchestrate drift detection and targeted re-rendering for a single file.\n *\n * The write strategy is determined by FORMAT_EMITTER_MAP:\n * - \"markers\" strategy: drift detection + writeWithMarkers (default)\n * - \"direct\" strategy: always regenerate + writeFile (full overwrite)\n *\n * Flow for markers strategy:\n * 1. File existence check — skip if missing\n * 2. Marker validation — abort on invalid markers\n * 3. Drift check (splice mode only) — skip if all claims GREEN\n * 4. Backup creation\n * 5. Re-render + writeWithMarkers\n *\n * Flow for direct strategy:\n * 1. File existence check — skip if missing\n * 2. Backup creation\n * 3. Re-render + writeFile (full overwrite)\n *\n * @param filePath Absolute path to the context file\n * @param registry Pre-computed convention registry (NOT extracted per-file)\n * @param bridge EzSearchBridge instance for drift scoring\n * @param confidenceThreshold Confidence floor passed to the renderer (default 0.7)\n */\nexport async function updateFile(\n filePath: string,\n registry: ConventionRegistry,\n bridge: EzSearchBridge,\n confidenceThreshold: number = 0.7\n): Promise<FileUpdateResult> {\n // 1. File existence check\n if (!existsSync(filePath)) {\n return { filePath, action: \"skipped\", reason: \"File does not exist\" };\n }\n\n const formatEntry = findFormatEntry(filePath);\n // Fall back to claude (markers) if the file isn't a known format\n const strategy = formatEntry?.strategy ?? \"markers\";\n const render = formatEntry?.render ?? FORMAT_EMITTER_MAP.claude.render;\n\n // ---------------------------------------------------------------------------\n // Direct strategy: full regeneration, no drift detection\n // ---------------------------------------------------------------------------\n if (strategy === \"direct\") {\n const backupPath = (await backupFile(filePath)) ?? undefined;\n const newContent = render(registry, confidenceThreshold);\n await writeFile(filePath, newContent, \"utf-8\");\n return {\n filePath,\n action: \"updated\",\n reason: \"Re-rendered (direct strategy)\",\n backupPath,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Markers strategy: drift detection + writeWithMarkers\n // ---------------------------------------------------------------------------\n\n // 2. Read content and validate markers\n const content = await readFile(filePath, \"utf-8\");\n const validation = validateMarkers(content);\n\n if (!validation.valid) {\n return { filePath, action: \"aborted\", reason: validation.reason! };\n }\n\n // 3. Drift check (only when markers are already present)\n if (validation.mode === \"splice\") {\n const claims = extractClaims(content, filePath);\n\n // Nothing to check — skip (no claims extracted means no drift to detect)\n if (claims.length === 0) {\n return { filePath, action: \"skipped\", reason: \"No drift detected\" };\n }\n\n const scored = await scoreClaims(claims, bridge);\n const hasDrift = scored.some((s) => s.status !== \"GREEN\");\n\n if (!hasDrift) {\n return { filePath, action: \"skipped\", reason: \"No drift detected\" };\n }\n }\n // mode === \"append\": file has no generated section yet -> always proceed\n\n // 4. Backup before any write\n const backupPath = (await backupFile(filePath)) ?? undefined;\n\n // 5. Re-render + write\n const newContent = render(registry, confidenceThreshold);\n await writeWithMarkers(filePath, newContent);\n\n return {\n filePath,\n action: \"updated\",\n reason: \"Re-rendered drifted sections\",\n backupPath,\n };\n}\n","import path from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { createBridge } from \"../core/ez-search-bridge.js\";\nimport { extractConventions } from \"../core/pipeline.js\";\nimport { updateFile } from \"../core/updater.js\";\nimport { extractClaims } from \"../core/drift/claim-extractor.js\";\nimport { scoreClaims } from \"../core/drift/claim-scorer.js\";\nimport { FORMAT_EMITTER_MAP } from \"../emitters/index.js\";\n\nexport async function updateAction(\n pathArg: string,\n options: { file?: string; dryRun?: boolean; yes?: boolean }\n): Promise<void> {\n const projectPath = path.resolve(pathArg);\n const spinner = ora(\"Checking for drift...\").start();\n\n try {\n const bridge = await createBridge(projectPath);\n\n // Always refresh so search results reflect current file state\n spinner.text = \"Refreshing search index...\";\n await bridge.refreshIndex(projectPath);\n\n // Resolve target files\n let filePaths: string[];\n if (options.file) {\n filePaths = [path.resolve(projectPath, options.file)];\n } else {\n filePaths = Object.values(FORMAT_EMITTER_MAP)\n .map((entry) => path.join(projectPath, entry.filename))\n .filter((p) => existsSync(p));\n }\n\n if (filePaths.length === 0) {\n spinner.fail(\"No context files found\");\n console.error(\n chalk.red(\"No generated context files found. Run 'ez-context generate' first, or use --file to specify one.\")\n );\n process.exit(1);\n }\n\n if (options.dryRun) {\n // Dry-run: analyze drift per file without writing\n spinner.succeed(\"Dry run complete\");\n console.log();\n console.log(chalk.bold.yellow(\"╔══════════════════════════════════════╗\"));\n console.log(chalk.bold.yellow(\"║ DRY RUN -- no files will be written ║\"));\n console.log(chalk.bold.yellow(\"╚══════════════════════════════════════╝\"));\n console.log();\n\n for (const filePath of filePaths) {\n const basename = path.basename(filePath);\n const content = await readFile(filePath, \"utf-8\");\n const claims = extractClaims(content, filePath);\n\n if (claims.length === 0) {\n console.log(` ${chalk.gray(\"-\")} ${basename} ${chalk.gray(\"(no claims to check)\")}`);\n continue;\n }\n\n const scored = await scoreClaims(claims, bridge);\n const hasDrift = scored.some((s) => s.status !== \"GREEN\");\n\n if (hasDrift) {\n console.log(` ${chalk.yellow(\"~\")} Would update ${chalk.cyan(basename)}`);\n } else {\n console.log(` ${chalk.gray(\"-\")} Up to date: ${chalk.gray(basename)}`);\n }\n }\n\n return;\n }\n\n // Real update: process each file\n spinner.text = \"Extracting conventions...\";\n const registry = await extractConventions(projectPath);\n const results = [];\n for (const filePath of filePaths) {\n const basename = path.basename(filePath);\n spinner.text = `Updating ${basename}...`;\n const result = await updateFile(filePath, registry, bridge);\n results.push(result);\n }\n\n // Summarize results\n const updated = results.filter((r) => r.action === \"updated\");\n const aborted = results.filter((r) => r.action === \"aborted\");\n\n if (updated.length === 0 && aborted.length === 0) {\n spinner.succeed(\"All context files are up to date\");\n } else if (updated.length > 0) {\n spinner.succeed(\n `Updated ${updated.length} file${updated.length === 1 ? \"\" : \"s\"}`\n );\n } else {\n spinner.fail(\"Update incomplete — some files could not be updated\");\n }\n\n // Per-file report\n console.log();\n for (const result of results) {\n const basename = path.basename(result.filePath);\n if (result.action === \"updated\") {\n const backup = result.backupPath ? ` (backup: ${path.basename(result.backupPath)})` : \"\";\n console.log(` ${chalk.green(\"✓\")} ${chalk.cyan(basename)}${chalk.gray(backup)}`);\n } else if (result.action === \"skipped\") {\n console.log(` ${chalk.gray(\"-\")} ${chalk.gray(basename)} ${chalk.gray(`(${result.reason})`)}`);\n } else {\n // aborted\n console.log(` ${chalk.yellow(\"⚠\")} ${chalk.yellow(basename)} ${chalk.yellow(`(${result.reason})`)}`);\n }\n }\n\n if (aborted.length > 0) {\n console.log();\n console.log(\n chalk.yellow(`Warning: ${aborted.length} file${aborted.length === 1 ? \"\" : \"s\"} could not be updated due to marker issues.`)\n );\n }\n\n } catch (err) {\n spinner.fail(\"Update failed\");\n const message = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(message));\n process.exit(1);\n }\n}\n","","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { generateAction } from \"./commands/generate.js\";\nimport { inspectAction } from \"./commands/inspect.js\";\nimport { driftAction } from \"./commands/drift.js\";\nimport { updateAction } from \"./commands/update.js\";\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nconst program = new Command();\n\nprogram\n .name(\"ez-context\")\n .description(\"Generate AI context files from any project\")\n .version(pkg.version);\n\nprogram\n .command(\"generate\")\n .description(\"Extract conventions and generate context files\")\n .argument(\"[path]\", \"project root to analyze\", \".\")\n .option(\"--dry-run\", \"preview without writing files\")\n .option(\"-y, --yes\", \"non-interactive mode\")\n .option(\"--output <dir>\", \"output directory\", \".\")\n .option(\"--threshold <number>\", \"confidence threshold 0-1\", \"0.7\")\n .option(\"--format <formats>\", \"output formats: claude,agents,cursor,copilot,skills,rulesync,ruler (comma-separated)\", \"claude,agents\")\n .action(generateAction);\n\nprogram\n .command(\"inspect\")\n .description(\"Display detected conventions\")\n .argument(\"[path]\", \"project root to analyze\", \".\")\n .option(\"--threshold <number>\", \"confidence threshold 0-1\", \"0.7\")\n .action(inspectAction);\n\nprogram\n .command(\"drift\")\n .description(\"Check context files against code for semantic drift\")\n .argument(\"[path]\", \"project root to analyze\", \".\")\n .option(\"--file <contextFile>\", \"specific context file to check\")\n .action(driftAction);\n\nprogram\n .command(\"update\")\n .description(\"Update drifted sections in context files, preserving manual edits\")\n .argument(\"[path]\", \"project root to analyze\", \".\")\n .option(\"--file <contextFile>\", \"specific context file to update\")\n .option(\"--dry-run\", \"preview changes without writing files\")\n .option(\"-y, --yes\", \"non-interactive mode\")\n .action(updateAction);\n\nawait program.parseAsync();\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":["pkg.version"],"sources":["../src/commands/generate.ts","../src/commands/inspect.ts","../src/core/drift/claim-extractor.ts","../src/core/drift/claim-scorer.ts","../src/core/drift/report.ts","../src/commands/drift.ts","../src/core/updater.ts","../src/commands/update.ts","../package.json","../src/cli.ts"],"sourcesContent":["import path from \"node:path\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { extractConventions } from \"../core/pipeline.js\";\nimport { emit } from \"../emitters/index.js\";\nimport type { EmitOptions, OutputFormat } from \"../emitters/types.js\";\n\nconst DRY_RUN_PREVIEW_LINES = 20;\n\nconst VALID_FORMATS: OutputFormat[] = [\n \"claude\",\n \"agents\",\n \"cursor\",\n \"copilot\",\n \"skills\",\n \"rulesync\",\n \"ruler\",\n];\n\nexport function parseFormats(raw: string): OutputFormat[] {\n const formats = [...new Set(raw.split(\",\").map((s) => s.trim()).filter(Boolean))];\n const invalid = formats.filter((f) => !VALID_FORMATS.includes(f as OutputFormat));\n if (invalid.length > 0) {\n throw new Error(\n `Invalid format(s): ${invalid.join(\", \")}. Valid: ${VALID_FORMATS.join(\", \")}`\n );\n }\n return formats as OutputFormat[];\n}\n\nfunction truncatePreview(content: string): string {\n const lines = content.split(\"\\n\");\n if (lines.length <= DRY_RUN_PREVIEW_LINES) {\n return content;\n }\n const preview = lines.slice(0, DRY_RUN_PREVIEW_LINES).join(\"\\n\");\n return `${preview}\\n... (${lines.length} lines total)`;\n}\n\nexport async function generateAction(\n pathArg: string,\n options: { dryRun?: boolean; yes?: boolean; output?: string; threshold?: string; format?: string }\n): Promise<void> {\n const projectPath = path.resolve(pathArg);\n\n // --yes and non-TTY environments: ora handles non-TTY gracefully by\n // falling back to plain text. No interactive prompts are used anywhere.\n const spinner = ora(\"Analyzing project conventions...\").start();\n\n try {\n const registry = await extractConventions(projectPath);\n const conventionCount = registry.conventions.length;\n spinner.succeed(`Found ${conventionCount} convention${conventionCount === 1 ? \"\" : \"s\"}`);\n\n const confidenceThreshold = parseFloat(options.threshold ?? \"0.7\");\n if (Number.isNaN(confidenceThreshold) || confidenceThreshold < 0 || confidenceThreshold > 1) {\n console.error(chalk.red(\"Invalid --threshold: must be a number between 0 and 1\"));\n process.exit(1);\n }\n const outputDir = path.resolve(options.output ?? \".\");\n\n // Parse and validate --format (default: \"claude,agents\")\n const formats = parseFormats(options.format ?? \"claude,agents\");\n\n const emitOptions: EmitOptions = {\n outputDir,\n confidenceThreshold,\n dryRun: options.dryRun ?? false,\n formats,\n };\n\n const isDefault = formats.length === 2 && formats.includes(\"claude\") && formats.includes(\"agents\");\n const genSpinnerText = isDefault\n ? \"Generating context files...\"\n : `Generating ${formats.length} context file${formats.length === 1 ? \"\" : \"s\"}...`;\n const genSpinner = ora(genSpinnerText).start();\n const result = await emit(registry, emitOptions);\n\n if (options.dryRun) {\n genSpinner.succeed(\"Dry run complete\");\n console.log();\n console.log(chalk.bold.yellow(\"╔══════════════════════════════════════╗\"));\n console.log(chalk.bold.yellow(\"║ DRY RUN -- no files will be written ║\"));\n console.log(chalk.bold.yellow(\"╚══════════════════════════════════════╝\"));\n console.log();\n for (const [format, content] of Object.entries(result.rendered)) {\n console.log(chalk.cyan(`--- ${format.toUpperCase()} ---`));\n console.log(truncatePreview(content));\n console.log();\n }\n } else {\n genSpinner.succeed(`Generated ${result.filesWritten.length} file${result.filesWritten.length === 1 ? \"\" : \"s\"}`);\n console.log();\n console.log(chalk.bold.green(\"Generated files:\"));\n for (const filePath of result.filesWritten) {\n const relPath = path.relative(outputDir, filePath);\n console.log(` ${chalk.cyan(relPath)}`);\n }\n }\n } catch (err) {\n spinner.fail(\"Analysis failed\");\n const message = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(message));\n process.exit(1);\n }\n}\n","import path from \"node:path\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { extractConventions } from \"../core/pipeline.js\";\nimport type { ConventionEntry } from \"../core/schema.js\";\n\nfunction confidenceDot(confidence: number): string {\n if (confidence >= 0.8) return chalk.green(\"●\");\n if (confidence >= 0.6) return chalk.yellow(\"●\");\n return chalk.red(\"●\");\n}\n\nexport async function inspectAction(\n pathArg: string,\n options: { threshold?: string }\n): Promise<void> {\n const projectPath = path.resolve(pathArg);\n const spinner = ora(\"Analyzing project conventions...\").start();\n\n try {\n const registry = await extractConventions(projectPath);\n const totalCount = registry.conventions.length;\n spinner.succeed(`Extracted ${totalCount} convention${totalCount === 1 ? \"\" : \"s\"}`);\n\n const threshold = parseFloat(options.threshold ?? \"0.7\");\n if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {\n console.error(chalk.red(\"Invalid --threshold: must be a number between 0 and 1\"));\n process.exit(1);\n }\n\n const filtered = registry.conventions.filter(\n (c) => c.confidence >= threshold\n );\n\n if (filtered.length === 0) {\n console.log(\n chalk.yellow(\n `\\nNo conventions found above ${threshold} confidence threshold. Try lowering --threshold.`\n )\n );\n return;\n }\n\n // Group by category\n const byCategory = new Map<string, ConventionEntry[]>();\n for (const convention of filtered) {\n const group = byCategory.get(convention.category) ?? [];\n group.push(convention);\n byCategory.set(convention.category, group);\n }\n\n console.log();\n for (const [category, conventions] of byCategory) {\n console.log(chalk.bold(category.toUpperCase()));\n for (const convention of conventions) {\n const pct = Math.round(convention.confidence * 100);\n console.log(\n ` ${confidenceDot(convention.confidence)} ${convention.pattern} ${chalk.gray(`(${pct}%)`)}`\n );\n }\n console.log();\n }\n\n const categoryCount = byCategory.size;\n console.log(\n chalk.gray(\n `Found ${filtered.length} convention${filtered.length === 1 ? \"\" : \"s\"} across ${categoryCount} categor${categoryCount === 1 ? \"y\" : \"ies\"} (threshold: ${threshold})`\n )\n );\n } catch (err) {\n spinner.fail(\"Analysis failed\");\n const message = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(message));\n process.exit(1);\n }\n}\n","/**\n * Claim extractor — parses markdown context files into individual testable claims.\n *\n * Input: raw markdown string (CLAUDE.md, AGENTS.md, .cursorrules, etc.)\n * Output: Claim[] where each claim is an atomic declarative statement\n *\n * Extraction rules:\n * - Bullet points: ^[-*+]\\s+\n * - Numbered list items: ^\\d+\\.\\s+\n * - Bold/code markers stripped from extracted text\n * - Boilerplate value lines skipped (Language: X, Framework: X, etc.)\n * - ez-context markers and HTML comments skipped\n * - Claims shorter than 10 chars or longer than 300 chars excluded\n * - Current section heading tracked for context\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface Claim {\n text: string; // The claim text (bold/code markers stripped)\n sourceFile: string; // Which file it came from\n sourceLine: number; // 1-based line number\n sourceSection: string; // Nearest parent heading\n}\n\n// ---------------------------------------------------------------------------\n// Filters\n// ---------------------------------------------------------------------------\n\n/**\n * Matches boilerplate key-value lines that are structural metadata, not\n * behavioral claims. Applied AFTER bold/code stripping.\n *\n * Examples skipped:\n * \"Language: TypeScript\"\n * \"Package Manager: bun\"\n */\nconst BOILERPLATE_VALUE =\n /^(Language|Framework|Build|Package Manager|Test Runner|Pattern|Layers):\\s/i;\n\n// ---------------------------------------------------------------------------\n// Core function\n// ---------------------------------------------------------------------------\n\n/**\n * Extract all testable claims from a markdown string.\n *\n * @param content Raw markdown content of the context file\n * @param sourceFile Path to the source file (stored on each claim)\n * @returns Array of extracted claims, filtered and deduplicated\n */\nexport function extractClaims(content: string, sourceFile: string): Claim[] {\n const claims: Claim[] = [];\n const lines = content.split(\"\\n\");\n let currentSection = \"\";\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!.trim();\n const lineNum = i + 1; // 1-based\n\n // Skip blank lines\n if (!line) continue;\n\n // Skip HTML comments (includes ez-context markers like <!-- ez-context:... -->)\n if (line.startsWith(\"<!--\")) continue;\n\n // Skip lines containing ez-context markers (belt-and-suspenders for inline markers)\n if (line.includes(\"ez-context:\")) continue;\n\n // Track section headings — H1, H2, H3\n const heading = line.match(/^#{1,3}\\s+(.+)/);\n if (heading) {\n currentSection = heading[1]!.trim();\n continue;\n }\n\n // Match bullet points or numbered list items\n const bullet = line.match(/^[-*+]\\s+(.+)/);\n const numbered = !bullet ? line.match(/^\\d+\\.\\s+(.+)/) : null;\n const rawText = bullet ? bullet[1]! : numbered ? numbered[1]! : null;\n\n if (!rawText) continue;\n\n // Strip bold markers (**text** -> text) and inline code markers (`text` -> text)\n const text = rawText\n .replace(/\\*\\*([^*]+)\\*\\*/g, \"$1\")\n .replace(/`([^`]+)`/g, \"$1\")\n .trim();\n\n // Apply length filters\n if (text.length < 10 || text.length > 300) continue;\n\n // Skip boilerplate key-value lines\n if (BOILERPLATE_VALUE.test(text)) continue;\n\n claims.push({\n text,\n sourceFile,\n sourceLine: lineNum,\n sourceSection: currentSection,\n });\n }\n\n return claims;\n}\n","/**\n * Claim scorer — compares extracted claims against the code index via semantic search.\n *\n * Each claim is searched against the indexed codebase. The top similarity score\n * determines whether the claim is GREEN (well-supported), YELLOW (possibly stale),\n * or RED (contradicted / not found).\n *\n * Claims are processed in batches to avoid ONNX pipeline contention.\n */\nimport type { Claim } from \"./claim-extractor.js\";\nimport type { SearchResult, EzSearchBridge } from \"../ez-search-bridge.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nexport const GREEN_THRESHOLD = 0.65;\nexport const YELLOW_THRESHOLD = 0.40;\nexport const BATCH_SIZE = 10;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ClaimStatus = \"GREEN\" | \"YELLOW\" | \"RED\";\n\nexport interface ScoredClaim {\n claim: Claim;\n status: ClaimStatus;\n score: number; // Top bridge.search() score (0.0-1.0)\n evidence: SearchResult[]; // Top k results\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction chunk<T>(arr: T[], size: number): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < arr.length; i += size) {\n chunks.push(arr.slice(i, i + size));\n }\n return chunks;\n}\n\nfunction classifyScore(score: number): ClaimStatus {\n if (score >= GREEN_THRESHOLD) return \"GREEN\";\n if (score >= YELLOW_THRESHOLD) return \"YELLOW\";\n return \"RED\";\n}\n\nasync function scoreSingleClaim(\n claim: Claim,\n bridge: EzSearchBridge\n): Promise<ScoredClaim> {\n const evidence = await bridge.search(claim.text, { k: 5 });\n const topScore = evidence.length > 0 ? evidence[0]!.score : 0;\n return {\n claim,\n status: classifyScore(topScore),\n score: topScore,\n evidence,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Score all claims by searching the code index in batches of BATCH_SIZE.\n *\n * @param claims Claims to score\n * @param bridge EzSearchBridge instance bound to the project\n * @param onProgress Optional callback fired after each batch: (done, total)\n * @returns ScoredClaim[] in the same order as input claims\n */\nexport async function scoreClaims(\n claims: Claim[],\n bridge: EzSearchBridge,\n onProgress?: (completed: number, total: number) => void\n): Promise<ScoredClaim[]> {\n const total = claims.length;\n const batches = chunk(claims, BATCH_SIZE);\n const results: ScoredClaim[] = [];\n let completed = 0;\n\n for (const batch of batches) {\n const batchResults = await Promise.all(\n batch.map((claim) => scoreSingleClaim(claim, bridge))\n );\n results.push(...batchResults);\n completed += batch.length;\n onProgress?.(completed, total);\n }\n\n return results;\n}\n","/**\n * Drift report — aggregates scored claims into a health score and markdown report.\n *\n * Health score: mean of per-claim scores scaled 0-100 (rounded).\n * Zero claims yields a health score of 100 (nothing to contradict = healthy).\n *\n * Rendered markdown groups claims by status with evidence for stale/contradicted claims.\n */\nimport type { ScoredClaim } from \"./claim-scorer.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DriftReport {\n sourceFile: string;\n healthScore: number;\n scoredClaims: ScoredClaim[];\n}\n\n// ---------------------------------------------------------------------------\n// Health score\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the aggregate health score for a set of scored claims.\n * Returns 100 for empty input (no claims = no drift).\n */\nexport function computeHealthScore(scoredClaims: ScoredClaim[]): number {\n if (scoredClaims.length === 0) return 100;\n const mean =\n scoredClaims.reduce((sum, sc) => sum + sc.score, 0) / scoredClaims.length;\n return Math.round(mean * 100);\n}\n\n// ---------------------------------------------------------------------------\n// Report assembly\n// ---------------------------------------------------------------------------\n\n/**\n * Build a DriftReport from a source file path and its scored claims.\n */\nexport function buildDriftReport(\n sourceFile: string,\n scoredClaims: ScoredClaim[]\n): DriftReport {\n return {\n sourceFile,\n healthScore: computeHealthScore(scoredClaims),\n scoredClaims,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Renderer\n// ---------------------------------------------------------------------------\n\nconst STATUS_LABEL: Record<string, string> = {\n GREEN: \"Confirmed\",\n YELLOW: \"Possibly Stale\",\n RED: \"Contradicted\",\n};\n\n/**\n * Render a drift report as a readable markdown string.\n *\n * Layout:\n * # Drift Report\n * Health score, source file, claim count\n *\n * ## Confirmed (GREEN)\n * - [GREEN] claim text (score: X.XX)\n *\n * ## Possibly Stale (YELLOW)\n * - [YELLOW] claim text (score: X.XX)\n * - file: chunk_preview\n *\n * ## Contradicted (RED)\n * - [RED] claim text (score: X.XX)\n * - file: chunk_preview\n *\n * Summary: X confirmed, Y possibly stale, Z contradicted\n */\nexport function renderDriftReport(report: DriftReport): string {\n const { sourceFile, healthScore, scoredClaims } = report;\n const lines: string[] = [];\n\n const green = scoredClaims.filter((sc) => sc.status === \"GREEN\");\n const yellow = scoredClaims.filter((sc) => sc.status === \"YELLOW\");\n const red = scoredClaims.filter((sc) => sc.status === \"RED\");\n\n // Header\n lines.push(\"# Drift Report\");\n lines.push(\"\");\n lines.push(`**Health Score:** ${healthScore}/100`);\n lines.push(`**File:** ${sourceFile}`);\n lines.push(`**Claims:** ${scoredClaims.length}`);\n lines.push(\"\");\n\n // Render a group of claims\n const renderGroup = (group: ScoredClaim[], status: string) => {\n if (group.length === 0) return;\n const label = STATUS_LABEL[status] ?? status;\n lines.push(`## ${label} (${status})`);\n lines.push(\"\");\n for (const sc of group) {\n lines.push(`- [${sc.status}] ${sc.claim.text} (score: ${sc.score.toFixed(2)})`);\n // Show top 2 evidence items for non-GREEN claims\n if (sc.status !== \"GREEN\") {\n const topEvidence = sc.evidence.slice(0, 2);\n for (const ev of topEvidence) {\n const preview = ev.chunk.replace(/\\s+/g, \" \").trim().slice(0, 80);\n lines.push(` - ${ev.file}: ${preview}`);\n }\n }\n }\n lines.push(\"\");\n };\n\n renderGroup(green, \"GREEN\");\n renderGroup(yellow, \"YELLOW\");\n renderGroup(red, \"RED\");\n\n // Summary\n lines.push(\n `Summary: ${green.length} confirmed, ${yellow.length} possibly stale, ${red.length} contradicted`\n );\n\n return lines.join(\"\\n\");\n}\n","import path from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { createBridge } from \"../core/ez-search-bridge.js\";\nimport { extractClaims } from \"../core/drift/claim-extractor.js\";\nimport { scoreClaims } from \"../core/drift/claim-scorer.js\";\nimport { buildDriftReport, renderDriftReport, computeHealthScore } from \"../core/drift/report.js\";\n\nconst CANDIDATE_FILES = [\"CLAUDE.md\", \"AGENTS.md\", \".cursorrules\", \"CONTEXT.md\"];\n\nfunction healthColor(score: number): string {\n if (score >= 70) return chalk.green(String(score));\n if (score >= 40) return chalk.yellow(String(score));\n return chalk.red(String(score));\n}\n\nexport async function driftAction(\n pathArg: string,\n options: { file?: string }\n): Promise<void> {\n const projectPath = path.resolve(pathArg);\n const spinner = ora(\"Loading context files...\").start();\n\n try {\n const bridge = await createBridge(projectPath);\n\n // Always refresh so search results reflect current file state\n spinner.text = \"Refreshing search index...\";\n await bridge.refreshIndex(projectPath);\n spinner.text = \"Loading context files...\";\n\n // Resolve files\n let filePaths: string[];\n if (options.file) {\n filePaths = [path.resolve(projectPath, options.file)];\n } else {\n filePaths = CANDIDATE_FILES\n .map((name) => path.join(projectPath, name))\n .filter((p) => existsSync(p));\n }\n\n if (filePaths.length === 0) {\n spinner.fail(\"No context files found\");\n console.error(\n chalk.red(\"No CLAUDE.md, AGENTS.md, .cursorrules, or CONTEXT.md found. Use --file to specify one.\")\n );\n process.exit(1);\n }\n\n // Extract claims from each file\n const claimsByFile: Map<string, ReturnType<typeof extractClaims>> = new Map();\n for (const filePath of filePaths) {\n const content = await readFile(filePath, \"utf-8\");\n const claims = extractClaims(content, filePath);\n claimsByFile.set(filePath, claims);\n }\n\n const allClaims = [...claimsByFile.values()].flat();\n spinner.text = `Analyzing ${allClaims.length} claims...`;\n\n // Score claims with progress callback\n const scoredAll = await scoreClaims(allClaims, bridge, (done, total) => {\n spinner.text = `Checking claim ${done}/${total}...`;\n });\n\n // Build and render reports per file\n const reports = filePaths.map((filePath) => {\n const fileClaims = claimsByFile.get(filePath) ?? [];\n const fileScoredClaims = scoredAll.filter((sc) =>\n fileClaims.some((c) => c === sc.claim)\n );\n return buildDriftReport(filePath, fileScoredClaims);\n });\n\n const overallScore = computeHealthScore(scoredAll);\n spinner.succeed(`Drift analysis complete — health score: ${healthColor(overallScore)}/100`);\n\n console.log();\n for (const report of reports) {\n console.log(renderDriftReport(report));\n console.log();\n }\n } catch (err) {\n spinner.fail(\"Drift analysis failed\");\n const message = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(message));\n if (message.includes(\"index\") || message.includes(\"embedding\") || message.includes(\"vector\")) {\n console.error(chalk.yellow(\"Hint: try deleting .ez-search/ and running again.\"));\n }\n process.exit(1);\n }\n}\n","/**\n * Updater — targeted regeneration engine for `ez-context update`.\n *\n * Orchestrates:\n * 1. Marker validation (pre-flight check, markers strategy only)\n * 2. Drift detection (skip GREEN files, markers strategy only)\n * 3. File backup (before any write)\n * 4. Re-rendering (via FORMAT_EMITTER_MAP)\n * 5. Write-back (writeWithMarkers for markers strategy, writeFile for direct)\n */\nimport { copyFile, readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { MARKER_START, MARKER_END, writeWithMarkers } from \"../emitters/writer.js\";\nimport { FORMAT_EMITTER_MAP } from \"../emitters/index.js\";\nimport { extractClaims } from \"./drift/claim-extractor.js\";\nimport { scoreClaims } from \"./drift/claim-scorer.js\";\nimport type { EzSearchBridge } from \"./ez-search-bridge.js\";\nimport type { ConventionRegistry } from \"./schema.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MarkerValidation {\n valid: boolean;\n mode: \"append\" | \"splice\" | \"invalid\";\n reason?: string;\n startIdx?: number;\n endIdx?: number;\n}\n\nexport type UpdateAction = \"skipped\" | \"updated\" | \"aborted\";\n\nexport interface FileUpdateResult {\n filePath: string;\n action: UpdateAction;\n reason: string;\n backupPath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// validateMarkers\n// ---------------------------------------------------------------------------\n\n/**\n * Pre-flight marker check for updateFile.\n *\n * Unlike writeWithMarkers (which silently appends on unpaired markers),\n * validateMarkers rejects unpaired markers so updateFile can abort safely.\n *\n * Returns:\n * - { valid: true, mode: \"append\" } — no markers, safe to append\n * - { valid: true, mode: \"splice\", startIdx, endIdx } — well-formed pair\n * - { valid: false, mode: \"invalid\", reason } — unpaired or inverted markers\n */\nexport function validateMarkers(content: string): MarkerValidation {\n const startIdx = content.indexOf(MARKER_START);\n const endIdx = content.indexOf(MARKER_END);\n\n const hasStart = startIdx !== -1;\n const hasEnd = endIdx !== -1;\n\n // No markers at all -> safe to append\n if (!hasStart && !hasEnd) {\n return { valid: true, mode: \"append\" };\n }\n\n // Both markers present -> validate ordering\n if (hasStart && hasEnd) {\n if (endIdx < startIdx) {\n return {\n valid: false,\n mode: \"invalid\",\n reason: \"End marker appears before start marker (corrupted file)\",\n };\n }\n return { valid: true, mode: \"splice\", startIdx, endIdx };\n }\n\n // Unpaired: only one marker present\n if (hasStart && !hasEnd) {\n return {\n valid: false,\n mode: \"invalid\",\n reason: \"Unpaired ez-context marker: end marker missing\",\n };\n }\n\n // hasEnd && !hasStart\n return {\n valid: false,\n mode: \"invalid\",\n reason: \"Unpaired ez-context marker: start marker missing\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// backupFile\n// ---------------------------------------------------------------------------\n\n/**\n * Copy filePath to filePath.bak and return the backup path.\n * Returns null if the file does not exist.\n * Overwrites any existing .bak silently (represents state before this run).\n */\nexport async function backupFile(filePath: string): Promise<string | null> {\n if (!existsSync(filePath)) {\n return null;\n }\n\n const backupPath = filePath + \".bak\";\n await copyFile(filePath, backupPath);\n return backupPath;\n}\n\n// ---------------------------------------------------------------------------\n// findFormatEntry\n// ---------------------------------------------------------------------------\n\n/**\n * Look up the FORMAT_EMITTER_MAP entry whose filename suffix matches filePath.\n * Returns undefined if the file doesn't correspond to a known format.\n */\nfunction findFormatEntry(filePath: string) {\n const normalized = path.normalize(filePath);\n for (const entry of Object.values(FORMAT_EMITTER_MAP)) {\n if (normalized.endsWith(path.normalize(entry.filename))) {\n return entry;\n }\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// updateFile\n// ---------------------------------------------------------------------------\n\n/**\n * Orchestrate drift detection and targeted re-rendering for a single file.\n *\n * The write strategy is determined by FORMAT_EMITTER_MAP:\n * - \"markers\" strategy: drift detection + writeWithMarkers (default)\n * - \"direct\" strategy: always regenerate + writeFile (full overwrite)\n *\n * Flow for markers strategy:\n * 1. File existence check — skip if missing\n * 2. Marker validation — abort on invalid markers\n * 3. Drift check (splice mode only) — skip if all claims GREEN\n * 4. Backup creation\n * 5. Re-render + writeWithMarkers\n *\n * Flow for direct strategy:\n * 1. File existence check — skip if missing\n * 2. Backup creation\n * 3. Re-render + writeFile (full overwrite)\n *\n * @param filePath Absolute path to the context file\n * @param registry Pre-computed convention registry (NOT extracted per-file)\n * @param bridge EzSearchBridge instance for drift scoring\n * @param confidenceThreshold Confidence floor passed to the renderer (default 0.7)\n */\nexport async function updateFile(\n filePath: string,\n registry: ConventionRegistry,\n bridge: EzSearchBridge,\n confidenceThreshold: number = 0.7\n): Promise<FileUpdateResult> {\n // 1. File existence check\n if (!existsSync(filePath)) {\n return { filePath, action: \"skipped\", reason: \"File does not exist\" };\n }\n\n const formatEntry = findFormatEntry(filePath);\n // Fall back to claude (markers) if the file isn't a known format\n const strategy = formatEntry?.strategy ?? \"markers\";\n const render = formatEntry?.render ?? FORMAT_EMITTER_MAP.claude.render;\n\n // ---------------------------------------------------------------------------\n // Direct strategy: full regeneration, no drift detection\n // ---------------------------------------------------------------------------\n if (strategy === \"direct\") {\n const backupPath = (await backupFile(filePath)) ?? undefined;\n const newContent = render(registry, confidenceThreshold);\n await writeFile(filePath, newContent, \"utf-8\");\n return {\n filePath,\n action: \"updated\",\n reason: \"Re-rendered (direct strategy)\",\n backupPath,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Markers strategy: drift detection + writeWithMarkers\n // ---------------------------------------------------------------------------\n\n // 2. Read content and validate markers\n const content = await readFile(filePath, \"utf-8\");\n const validation = validateMarkers(content);\n\n if (!validation.valid) {\n return { filePath, action: \"aborted\", reason: validation.reason! };\n }\n\n // 3. Drift check (only when markers are already present)\n if (validation.mode === \"splice\") {\n const claims = extractClaims(content, filePath);\n\n // Nothing to check — skip (no claims extracted means no drift to detect)\n if (claims.length === 0) {\n return { filePath, action: \"skipped\", reason: \"No drift detected\" };\n }\n\n const scored = await scoreClaims(claims, bridge);\n const hasDrift = scored.some((s) => s.status !== \"GREEN\");\n\n if (!hasDrift) {\n return { filePath, action: \"skipped\", reason: \"No drift detected\" };\n }\n }\n // mode === \"append\": file has no generated section yet -> always proceed\n\n // 4. Backup before any write\n const backupPath = (await backupFile(filePath)) ?? undefined;\n\n // 5. Re-render + write\n const newContent = render(registry, confidenceThreshold);\n await writeWithMarkers(filePath, newContent);\n\n return {\n filePath,\n action: \"updated\",\n reason: \"Re-rendered drifted sections\",\n backupPath,\n };\n}\n","import path from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { createBridge } from \"../core/ez-search-bridge.js\";\nimport { extractConventions } from \"../core/pipeline.js\";\nimport { updateFile } from \"../core/updater.js\";\nimport { extractClaims } from \"../core/drift/claim-extractor.js\";\nimport { scoreClaims } from \"../core/drift/claim-scorer.js\";\nimport { FORMAT_EMITTER_MAP } from \"../emitters/index.js\";\n\nexport async function updateAction(\n pathArg: string,\n options: { file?: string; dryRun?: boolean; yes?: boolean }\n): Promise<void> {\n const projectPath = path.resolve(pathArg);\n const spinner = ora(\"Checking for drift...\").start();\n\n try {\n const bridge = await createBridge(projectPath);\n\n // Always refresh so search results reflect current file state\n spinner.text = \"Refreshing search index...\";\n await bridge.refreshIndex(projectPath);\n\n // Resolve target files\n let filePaths: string[];\n if (options.file) {\n filePaths = [path.resolve(projectPath, options.file)];\n } else {\n filePaths = Object.values(FORMAT_EMITTER_MAP)\n .map((entry) => path.join(projectPath, entry.filename))\n .filter((p) => existsSync(p));\n }\n\n if (filePaths.length === 0) {\n spinner.fail(\"No context files found\");\n console.error(\n chalk.red(\"No generated context files found. Run 'ez-context generate' first, or use --file to specify one.\")\n );\n process.exit(1);\n }\n\n if (options.dryRun) {\n // Dry-run: analyze drift per file without writing\n spinner.succeed(\"Dry run complete\");\n console.log();\n console.log(chalk.bold.yellow(\"╔══════════════════════════════════════╗\"));\n console.log(chalk.bold.yellow(\"║ DRY RUN -- no files will be written ║\"));\n console.log(chalk.bold.yellow(\"╚══════════════════════════════════════╝\"));\n console.log();\n\n for (const filePath of filePaths) {\n const basename = path.basename(filePath);\n const content = await readFile(filePath, \"utf-8\");\n const claims = extractClaims(content, filePath);\n\n if (claims.length === 0) {\n console.log(` ${chalk.gray(\"-\")} ${basename} ${chalk.gray(\"(no claims to check)\")}`);\n continue;\n }\n\n const scored = await scoreClaims(claims, bridge);\n const hasDrift = scored.some((s) => s.status !== \"GREEN\");\n\n if (hasDrift) {\n console.log(` ${chalk.yellow(\"~\")} Would update ${chalk.cyan(basename)}`);\n } else {\n console.log(` ${chalk.gray(\"-\")} Up to date: ${chalk.gray(basename)}`);\n }\n }\n\n return;\n }\n\n // Real update: process each file\n spinner.text = \"Extracting conventions...\";\n const registry = await extractConventions(projectPath);\n const results = [];\n for (const filePath of filePaths) {\n const basename = path.basename(filePath);\n spinner.text = `Updating ${basename}...`;\n const result = await updateFile(filePath, registry, bridge);\n results.push(result);\n }\n\n // Summarize results\n const updated = results.filter((r) => r.action === \"updated\");\n const aborted = results.filter((r) => r.action === \"aborted\");\n\n if (updated.length === 0 && aborted.length === 0) {\n spinner.succeed(\"All context files are up to date\");\n } else if (updated.length > 0) {\n spinner.succeed(\n `Updated ${updated.length} file${updated.length === 1 ? \"\" : \"s\"}`\n );\n } else {\n spinner.fail(\"Update incomplete — some files could not be updated\");\n }\n\n // Per-file report\n console.log();\n for (const result of results) {\n const basename = path.basename(result.filePath);\n if (result.action === \"updated\") {\n const backup = result.backupPath ? ` (backup: ${path.basename(result.backupPath)})` : \"\";\n console.log(` ${chalk.green(\"✓\")} ${chalk.cyan(basename)}${chalk.gray(backup)}`);\n } else if (result.action === \"skipped\") {\n console.log(` ${chalk.gray(\"-\")} ${chalk.gray(basename)} ${chalk.gray(`(${result.reason})`)}`);\n } else {\n // aborted\n console.log(` ${chalk.yellow(\"⚠\")} ${chalk.yellow(basename)} ${chalk.yellow(`(${result.reason})`)}`);\n }\n }\n\n if (aborted.length > 0) {\n console.log();\n console.log(\n chalk.yellow(`Warning: ${aborted.length} file${aborted.length === 1 ? \"\" : \"s\"} could not be updated due to marker issues.`)\n );\n }\n\n } catch (err) {\n spinner.fail(\"Update failed\");\n const message = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(message));\n if (message.includes(\"index\") || message.includes(\"embedding\") || message.includes(\"vector\")) {\n console.error(chalk.yellow(\"Hint: try deleting .ez-search/ and running again.\"));\n }\n process.exit(1);\n }\n}\n","","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { generateAction } from \"./commands/generate.js\";\nimport { inspectAction } from \"./commands/inspect.js\";\nimport { driftAction } from \"./commands/drift.js\";\nimport { updateAction } from \"./commands/update.js\";\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nconst program = new Command();\n\nprogram\n .name(\"ez-context\")\n .description(\"Generate AI context files from any project\")\n .version(pkg.version);\n\nprogram\n .command(\"generate\")\n .description(\"Extract conventions and generate context files\")\n .argument(\"[path]\", \"project root to analyze\", \".\")\n .option(\"--dry-run\", \"preview without writing files\")\n .option(\"-y, --yes\", \"non-interactive mode\")\n .option(\"--output <dir>\", \"output directory\", \".\")\n .option(\"--threshold <number>\", \"confidence threshold 0-1\", \"0.7\")\n .option(\"--format <formats>\", \"output formats: claude,agents,cursor,copilot,skills,rulesync,ruler (comma-separated)\", \"claude,agents\")\n .action(generateAction);\n\nprogram\n .command(\"inspect\")\n .description(\"Display detected conventions\")\n .argument(\"[path]\", \"project root to analyze\", \".\")\n .option(\"--threshold <number>\", \"confidence threshold 0-1\", \"0.7\")\n .action(inspectAction);\n\nprogram\n .command(\"drift\")\n .description(\"Check context files against code for semantic drift\")\n .argument(\"[path]\", \"project root to analyze\", \".\")\n .option(\"--file <contextFile>\", \"specific context file to check\")\n .action(driftAction);\n\nprogram\n .command(\"update\")\n .description(\"Update drifted sections in context files, preserving manual edits\")\n .argument(\"[path]\", \"project root to analyze\", \".\")\n .option(\"--file <contextFile>\", \"specific context file to update\")\n .option(\"--dry-run\", \"preview changes without writing files\")\n .option(\"-y, --yes\", \"non-interactive mode\")\n .action(updateAction);\n\nawait program.parseAsync();\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ import { globby } from "globby";
6
6
  import yaml, { load } from "js-yaml";
7
7
  import { Project, SyntaxKind } from "ts-morph";
8
8
  import { EzSearchError, index, query } from "@ez-corp/ez-search";
9
- import { existsSync } from "node:fs";
9
+ import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
10
10
 
11
11
  //#region src/core/schema.ts
12
12
  const ConventionCategorySchema = z.enum([
@@ -1043,6 +1043,60 @@ const staticErrorHandlingExtractor = {
1043
1043
  * This is the ONLY file that imports from @ez-corp/ez-search.
1044
1044
  * All other modules interact with ez-search via the EzSearchBridge interface.
1045
1045
  */
1046
+ /**
1047
+ * Check if a .ez-search/ index directory appears corrupt.
1048
+ *
1049
+ * Zvec (the native vector DB) will SIGABRT if it opens a collection with
1050
+ * corrupt .proxima files. Since SIGABRT kills the process before JS can
1051
+ * catch, we proactively detect corruption and wipe the index so it can
1052
+ * be rebuilt cleanly.
1053
+ *
1054
+ * Heuristic: a segment directory containing a .proxima file alongside a
1055
+ * zero-byte .ipc file indicates an interrupted or corrupt write.
1056
+ */
1057
+ function isIndexCorrupt(indexDir) {
1058
+ let entries;
1059
+ try {
1060
+ entries = readdirSync(indexDir);
1061
+ } catch {
1062
+ return false;
1063
+ }
1064
+ for (const entry of entries) {
1065
+ if (!entry.startsWith("col-")) continue;
1066
+ const colDir = join(indexDir, entry);
1067
+ let segments;
1068
+ try {
1069
+ segments = readdirSync(colDir);
1070
+ } catch {
1071
+ continue;
1072
+ }
1073
+ for (const seg of segments) {
1074
+ const segPath = join(colDir, seg);
1075
+ try {
1076
+ if (!statSync(segPath).isDirectory()) continue;
1077
+ } catch {
1078
+ continue;
1079
+ }
1080
+ let files;
1081
+ try {
1082
+ files = readdirSync(segPath);
1083
+ } catch {
1084
+ continue;
1085
+ }
1086
+ const hasProxima = files.some((f) => f.endsWith(".proxima"));
1087
+ const hasZeroByteIpc = files.some((f) => {
1088
+ if (!f.endsWith(".ipc")) return false;
1089
+ try {
1090
+ return statSync(join(segPath, f)).size === 0;
1091
+ } catch {
1092
+ return false;
1093
+ }
1094
+ });
1095
+ if (hasProxima && hasZeroByteIpc) return true;
1096
+ }
1097
+ }
1098
+ return false;
1099
+ }
1046
1100
  var EzSearchBridgeImpl = class {
1047
1101
  constructor(projectPath) {
1048
1102
  this.projectPath = projectPath;
@@ -1055,7 +1109,22 @@ var EzSearchBridgeImpl = class {
1055
1109
  await index(projectPath);
1056
1110
  }
1057
1111
  async refreshIndex(projectPath) {
1058
- await index(projectPath);
1112
+ const indexDir = join(projectPath, ".ez-search");
1113
+ if (existsSync(indexDir) && isIndexCorrupt(indexDir)) rmSync(indexDir, {
1114
+ recursive: true,
1115
+ force: true
1116
+ });
1117
+ try {
1118
+ await index(projectPath);
1119
+ } catch (err) {
1120
+ if (existsSync(indexDir)) {
1121
+ rmSync(indexDir, {
1122
+ recursive: true,
1123
+ force: true
1124
+ });
1125
+ await index(projectPath);
1126
+ } else throw err;
1127
+ }
1059
1128
  }
1060
1129
  async search(searchQuery, options = {}) {
1061
1130
  const { k = 10 } = options;
@@ -1643,7 +1712,10 @@ function renderAgentsMd(registry, confidenceThreshold) {
1643
1712
  lines.push("");
1644
1713
  lines.push("## Testing");
1645
1714
  if (data.testRunner) lines.push(`- Test runner: ${data.testRunner}`);
1646
- for (const entry of data.testingConventions) lines.push(`- ${entry.pattern}`);
1715
+ for (const entry of data.testingConventions) {
1716
+ if (isRedundantConvention(entry)) continue;
1717
+ lines.push(`- ${entry.pattern}`);
1718
+ }
1647
1719
  }
1648
1720
  if (data.hasProjectStructure) {
1649
1721
  lines.push("");
@@ -1848,5 +1920,5 @@ async function emit(registry, options) {
1848
1920
  }
1849
1921
 
1850
1922
  //#endregion
1851
- export { EvidenceRefSchema as _, writeWithMarkers as a, ALWAYS_SKIP as c, addConvention as d, createRegistry as f, ConventionRegistrySchema as g, ConventionEntrySchema as h, MARKER_START as i, listProjectFiles as l, ConventionCategorySchema as m, emit as n, extractConventions as o, ArchitectureInfoSchema as p, MARKER_END as r, createBridge as s, FORMAT_EMITTER_MAP as t, runExtractors as u, StackInfoSchema as v };
1852
- //# sourceMappingURL=emitters-DOtul0uF.js.map
1923
+ export { ConventionRegistrySchema as C, ConventionEntrySchema as S, StackInfoSchema as T, runExtractors as _, renderSkillMd as a, ArchitectureInfoSchema as b, renderAgentsMd as c, MARKER_START as d, writeWithMarkers as f, listProjectFiles as g, ALWAYS_SKIP as h, renderRulesyncMd as i, renderClaudeMd as l, createBridge as m, emit as n, renderCopilotMd as o, extractConventions as p, renderRulerMd as r, renderCursorMdc as s, FORMAT_EMITTER_MAP as t, MARKER_END as u, addConvention as v, EvidenceRefSchema as w, ConventionCategorySchema as x, createRegistry as y };
1924
+ //# sourceMappingURL=emitters-Csij2cRu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitters-Csij2cRu.js","names":["EVIDENCE","parseToml","yamlLoad","prepData"],"sources":["../src/core/schema.ts","../src/core/registry.ts","../src/extractors/index.ts","../src/extractors/static/package-json.ts","../src/extractors/static/lockfile.ts","../src/extractors/static/tsconfig.ts","../src/extractors/static/go-mod.ts","../src/extractors/static/cargo-toml.ts","../src/extractors/static/ci.ts","../src/utils/fs.ts","../src/extractors/static/project-structure.ts","../src/extractors/code/naming.ts","../src/extractors/code/imports.ts","../src/extractors/code/error-handling.ts","../src/core/ez-search-bridge.ts","../src/extractors/semantic/error-handling.ts","../src/extractors/semantic/architecture.ts","../src/core/pipeline.ts","../src/emitters/writer.ts","../src/emitters/render-helpers.ts","../src/emitters/claude-md.ts","../src/emitters/agents-md.ts","../src/emitters/cursor-mdc.ts","../src/emitters/copilot-md.ts","../src/emitters/skill-md.ts","../src/emitters/rulesync-md.ts","../src/emitters/ruler-md.ts","../src/emitters/index.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Atomic schemas\n// ---------------------------------------------------------------------------\n\nexport const ConventionCategorySchema = z.enum([\n \"stack\",\n \"naming\",\n \"architecture\",\n \"error_handling\",\n \"testing\",\n \"imports\",\n \"other\",\n]);\n\nexport const EvidenceRefSchema = z.object({\n file: z.string(),\n line: z.number().int().positive().nullable(),\n});\n\nexport const ConventionEntrySchema = z.object({\n id: z.uuid(),\n category: ConventionCategorySchema,\n pattern: z.string().min(1),\n confidence: z.number().min(0).max(1),\n evidence: z.array(EvidenceRefSchema),\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport const StackInfoSchema = z.object({\n language: z.string(),\n framework: z.string().optional(),\n testRunner: z.string().optional(),\n buildTool: z.string().optional(),\n packageManager: z.string().optional(),\n nodeVersion: z.string().optional(),\n});\n\nexport const ArchitectureInfoSchema = z.object({\n pattern: z.string().optional(),\n layers: z.array(z.string()),\n entryPoints: z.array(z.string()).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Root registry schema\n// ---------------------------------------------------------------------------\n\nexport const ConventionRegistrySchema = z.object({\n version: z.literal(\"1\"),\n projectPath: z.string(),\n generatedAt: z.string().datetime(),\n stack: StackInfoSchema,\n conventions: z.array(ConventionEntrySchema),\n architecture: ArchitectureInfoSchema,\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Inferred types (no manual interfaces)\n// ---------------------------------------------------------------------------\n\nexport type ConventionCategory = z.infer<typeof ConventionCategorySchema>;\nexport type EvidenceRef = z.infer<typeof EvidenceRefSchema>;\nexport type ConventionEntry = z.infer<typeof ConventionEntrySchema>;\nexport type StackInfo = z.infer<typeof StackInfoSchema>;\nexport type ArchitectureInfo = z.infer<typeof ArchitectureInfoSchema>;\nexport type ConventionRegistry = z.infer<typeof ConventionRegistrySchema>;\n","import {\n type ConventionEntry,\n type ConventionRegistry,\n ConventionRegistrySchema,\n} from \"./schema.js\";\n\n/**\n * Create a new empty ConventionRegistry for the given project path.\n * The returned registry passes ConventionRegistrySchema validation.\n */\nexport function createRegistry(projectPath: string): ConventionRegistry {\n const registry: ConventionRegistry = {\n version: \"1\",\n projectPath,\n generatedAt: new Date().toISOString(),\n stack: {\n language: \"unknown\",\n },\n conventions: [],\n architecture: {\n layers: [],\n },\n };\n\n const result = ConventionRegistrySchema.safeParse(registry);\n if (!result.success) {\n throw new Error(\n `createRegistry produced invalid registry: ${JSON.stringify(result.error.issues)}`\n );\n }\n\n return result.data;\n}\n\n/**\n * Add a convention entry to the registry, auto-generating a UUID for the id.\n * Returns a new registry (does not mutate the input).\n */\nexport function addConvention(\n registry: ConventionRegistry,\n entry: Omit<ConventionEntry, \"id\">\n): ConventionRegistry {\n const newEntry: ConventionEntry = {\n id: crypto.randomUUID(),\n ...entry,\n };\n\n const updated: ConventionRegistry = {\n ...registry,\n conventions: [...registry.conventions, newEntry],\n };\n\n const result = ConventionRegistrySchema.safeParse(updated);\n if (!result.success) {\n throw new Error(\n `addConvention produced invalid registry: ${JSON.stringify(result.error.issues)}`\n );\n }\n\n return result.data;\n}\n","import { addConvention } from \"../core/registry.js\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"./types.js\";\n\n/**\n * Run all extractors in parallel via Promise.allSettled so that a single\n * failing extractor does not abort the others.\n *\n * Fulfilled entries are added to the registry immutably via `addConvention`.\n * Rejected extractors emit a console.warn and are skipped.\n */\nexport async function runExtractors(\n extractors: Extractor[],\n ctx: ExtractionContext,\n registry: ConventionRegistry\n): Promise<ConventionRegistry> {\n const results = await Promise.allSettled(\n extractors.map((e) => e.extract(ctx).then((entries) => ({ extractor: e, entries })))\n );\n\n let current = registry;\n\n for (const [i, result] of results.entries()) {\n if (result.status === \"fulfilled\") {\n for (const entry of result.value.entries) {\n current = addConvention(current, entry);\n }\n } else {\n console.warn(\n `[runExtractors] Extractor \"${extractors[i]!.name}\" failed:`,\n result.reason\n );\n }\n }\n\n return current;\n}\n","import { access, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Detection maps\n// ---------------------------------------------------------------------------\n\nconst FRAMEWORK_MAP: Record<string, string> = {\n react: \"React\",\n vue: \"Vue\",\n \"@angular/core\": \"Angular\",\n next: \"Next.js\",\n nuxt: \"Nuxt\",\n svelte: \"Svelte\",\n hono: \"Hono\",\n express: \"Express\",\n fastify: \"Fastify\",\n koa: \"Koa\",\n};\n\nconst TEST_RUNNER_MAP: Record<string, string> = {\n vitest: \"Vitest\",\n jest: \"Jest\",\n mocha: \"Mocha\",\n jasmine: \"Jasmine\",\n ava: \"Ava\",\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ntype PackageJson = {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n scripts?: Record<string, string>;\n type?: string;\n packageManager?: string;\n};\n\nfunction allDeps(pkg: PackageJson): Record<string, string> {\n return { ...pkg.dependencies, ...pkg.devDependencies };\n}\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\nconst EVIDENCE = [{ file: \"package.json\", line: null }];\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const packageJsonExtractor: Extractor = {\n name: \"package-json\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const filePath = join(ctx.projectPath, \"package.json\");\n\n try {\n await access(filePath);\n } catch {\n return [];\n }\n\n let pkg: PackageJson;\n try {\n const raw = await readFile(filePath, \"utf-8\");\n pkg = JSON.parse(raw) as PackageJson;\n } catch {\n return [];\n }\n\n const deps = allDeps(pkg);\n const entries: Entry[] = [];\n\n // --- Language detection ---\n const isTypeScript =\n \"typescript\" in deps || \"@types/node\" in deps;\n const language = isTypeScript ? \"TypeScript\" : \"JavaScript\";\n entries.push({\n category: \"stack\",\n pattern: `Language: ${language}`,\n confidence: 0.95,\n evidence: EVIDENCE,\n metadata: { language },\n });\n\n // --- Framework detection ---\n for (const [pkg_name, label] of Object.entries(FRAMEWORK_MAP)) {\n if (pkg_name in deps) {\n const version = deps[pkg_name];\n entries.push({\n category: \"stack\",\n pattern: `Framework: ${label}`,\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { framework: label, version },\n });\n break; // first match wins\n }\n }\n\n // --- Test runner detection ---\n for (const [pkg_name, label] of Object.entries(TEST_RUNNER_MAP)) {\n if (pkg_name in deps) {\n entries.push({\n category: \"testing\",\n pattern: `Test runner: ${label}`,\n confidence: 0.95,\n evidence: EVIDENCE,\n metadata: { testRunner: label },\n });\n break; // first match wins\n }\n }\n\n // --- Package manager detection (corepack \"packageManager\" field) ---\n if (typeof pkg.packageManager === \"string\") {\n const pmName = pkg.packageManager.split(\"@\")[0];\n if (pmName) {\n entries.push({\n category: \"stack\",\n pattern: `Package manager: ${pmName}`,\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { packageManager: pmName },\n });\n }\n }\n\n // --- ESM module system detection ---\n if (pkg.type === \"module\") {\n entries.push({\n category: \"imports\",\n pattern: `ES modules (package.json \"type\": \"module\")`,\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { moduleSystem: \"esm\" },\n });\n }\n\n // --- Scripts ---\n const scripts = pkg.scripts ?? {};\n for (const [scriptName, command] of Object.entries(scripts)) {\n const isTestScript =\n scriptName === \"test\" || scriptName.startsWith(\"test:\");\n const isBuildOrLint =\n scriptName === \"build\" || scriptName === \"lint\";\n\n if (isTestScript || isBuildOrLint) {\n entries.push({\n category: isTestScript ? \"testing\" : \"stack\",\n pattern: `Script \"${scriptName}\": ${command}`,\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { scriptName, command },\n });\n }\n }\n\n return entries;\n },\n};\n","import { access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Lockfile → package manager mapping (priority order)\n// ---------------------------------------------------------------------------\n\nconst LOCKFILES: Array<{ file: string; manager: string }> = [\n { file: \"bun.lock\", manager: \"bun\" },\n { file: \"bun.lockb\", manager: \"bun\" },\n { file: \"pnpm-lock.yaml\", manager: \"pnpm\" },\n { file: \"yarn.lock\", manager: \"yarn\" },\n { file: \"package-lock.json\", manager: \"npm\" },\n];\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\nexport const lockfileExtractor: Extractor = {\n name: \"lockfile\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n for (const { file, manager } of LOCKFILES) {\n try {\n await access(join(ctx.projectPath, file));\n return [\n {\n category: \"stack\",\n pattern: `Package manager: ${manager}`,\n confidence: 1.0,\n evidence: [{ file, line: null }],\n metadata: { packageManager: manager },\n },\n ];\n } catch {\n // not found, try next\n }\n }\n\n return [];\n },\n};\n","import { access, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype CompilerOptions = {\n strict?: boolean;\n target?: string;\n module?: string;\n moduleResolution?: string;\n paths?: Record<string, string[]>;\n [key: string]: unknown;\n};\n\ntype TsConfig = {\n compilerOptions?: CompilerOptions;\n [key: string]: unknown;\n};\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst EVIDENCE = [{ file: \"tsconfig.json\", line: null }];\n\n/**\n * Strip single-line // comments and trailing commas so JSON.parse accepts\n * tsconfig.json files that use non-standard JSON.\n */\nfunction stripTsConfigNonStandardJson(raw: string): string {\n return raw\n // Remove single-line comments (// ...)\n .replace(/\\/\\/[^\\n]*/g, \"\")\n // Remove trailing commas before ] or }\n .replace(/,(\\s*[}\\]])/g, \"$1\");\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const tsconfigExtractor: Extractor = {\n name: \"tsconfig\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const filePath = join(ctx.projectPath, \"tsconfig.json\");\n\n try {\n await access(filePath);\n } catch {\n return [];\n }\n\n let config: TsConfig;\n try {\n const raw = await readFile(filePath, \"utf-8\");\n config = JSON.parse(stripTsConfigNonStandardJson(raw)) as TsConfig;\n } catch {\n return [];\n }\n\n const co = config.compilerOptions ?? {};\n const entries: Entry[] = [];\n\n // --- Strict mode ---\n if (co.strict === true) {\n entries.push({\n category: \"stack\",\n pattern: \"TypeScript strict mode enabled\",\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { strict: true },\n });\n }\n\n // --- Notable compiler options ---\n const notable: Record<string, unknown> = {};\n for (const key of [\"target\", \"module\", \"moduleResolution\"] as const) {\n if (co[key] !== undefined) {\n notable[key] = co[key];\n }\n }\n if (Object.keys(notable).length > 0) {\n entries.push({\n category: \"stack\",\n pattern: \"TypeScript compiler options configured\",\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: notable,\n });\n }\n\n // --- Path aliases ---\n if (co.paths && Object.keys(co.paths).length > 0) {\n entries.push({\n category: \"imports\",\n pattern: \"Uses TypeScript path aliases\",\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { aliases: co.paths },\n });\n }\n\n return entries;\n },\n};\n","import { access, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\nexport const goModExtractor: Extractor = {\n name: \"go-mod\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const filePath = join(ctx.projectPath, \"go.mod\");\n\n try {\n await access(filePath);\n } catch {\n return [];\n }\n\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf-8\");\n } catch {\n return [];\n }\n\n const lines = raw.split(\"\\n\");\n\n let moduleName = \"\";\n let goVersion = \"\";\n let dependencyCount = 0;\n let inRequireBlock = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (!moduleName) {\n const moduleMatch = trimmed.match(/^module\\s+(\\S+)/);\n if (moduleMatch?.[1]) {\n moduleName = moduleMatch[1];\n continue;\n }\n }\n\n if (!goVersion) {\n const goMatch = trimmed.match(/^go\\s+(\\S+)/);\n if (goMatch?.[1]) {\n goVersion = goMatch[1];\n continue;\n }\n }\n\n // Count deps in require blocks\n if (trimmed === \"require (\") {\n inRequireBlock = true;\n continue;\n }\n if (inRequireBlock) {\n if (trimmed === \")\") {\n inRequireBlock = false;\n } else if (trimmed.length > 0 && !trimmed.startsWith(\"//\")) {\n dependencyCount++;\n }\n continue;\n }\n // Single-line require\n if (trimmed.match(/^require\\s+\\S+\\s+v\\S+/)) {\n dependencyCount++;\n }\n }\n\n if (!moduleName) {\n return [];\n }\n\n return [\n {\n category: \"stack\",\n pattern: `Go project (${moduleName})`,\n confidence: 1.0,\n evidence: [{ file: \"go.mod\", line: null }],\n metadata: {\n language: \"Go\",\n moduleName,\n goVersion: goVersion || null,\n dependencyCount,\n },\n },\n ];\n },\n};\n","import { access, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parse as parseToml } from \"smol-toml\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\ntype CargoToml = {\n package?: { name?: string; version?: string };\n dependencies?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport const cargoTomlExtractor: Extractor = {\n name: \"cargo-toml\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const filePath = join(ctx.projectPath, \"Cargo.toml\");\n\n try {\n await access(filePath);\n } catch {\n return [];\n }\n\n let cargo: CargoToml;\n try {\n const raw = await readFile(filePath, \"utf-8\");\n cargo = parseToml(raw) as CargoToml;\n } catch {\n return [];\n }\n\n const packageName = cargo.package?.name;\n if (!packageName) {\n return [];\n }\n\n const dependencyCount = Object.keys(cargo.dependencies ?? {}).length;\n\n return [\n {\n category: \"stack\",\n pattern: `Rust project (${packageName})`,\n confidence: 1.0,\n evidence: [{ file: \"Cargo.toml\", line: null }],\n metadata: {\n language: \"Rust\",\n packageName,\n dependencyCount,\n },\n },\n ];\n },\n};\n","import { readFile } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport { globby } from \"globby\";\nimport { load as yamlLoad } from \"js-yaml\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ntype CommandCategory = \"stack\" | \"testing\";\n\ninterface CommandMatch {\n command: string;\n category: CommandCategory;\n ciFile: string;\n}\n\nconst BUILD_KEYWORDS = [\"build\", \"compile\", \"tsc\", \"tsdown\"];\nconst TEST_KEYWORDS = [\"test\", \"vitest\", \"jest\", \"pytest\", \"cargo test\", \"go test\"];\nconst LINT_KEYWORDS = [\"lint\", \"eslint\", \"biome\", \"clippy\", \"ruff\"];\n\nfunction categorizeCommand(cmd: string): CommandCategory | null {\n const lower = cmd.toLowerCase();\n if (TEST_KEYWORDS.some((kw) => lower.includes(kw))) return \"testing\";\n if (BUILD_KEYWORDS.some((kw) => lower.includes(kw))) return \"stack\";\n if (LINT_KEYWORDS.some((kw) => lower.includes(kw))) return \"stack\";\n return null;\n}\n\n/**\n * Extract run commands from a GitHub Actions workflow YAML.\n * Traverses jobs.*.steps[].run\n */\nfunction extractGithubActionsCommands(doc: unknown, filePath: string): { matched: CommandMatch[]; raw: string[] } {\n const matched: CommandMatch[] = [];\n const raw: string[] = [];\n\n if (!doc || typeof doc !== \"object\") return { matched, raw };\n const record = doc as Record<string, unknown>;\n const jobs = record[\"jobs\"];\n if (!jobs || typeof jobs !== \"object\") return { matched, raw };\n\n for (const job of Object.values(jobs as Record<string, unknown>)) {\n if (!job || typeof job !== \"object\") continue;\n const steps = (job as Record<string, unknown>)[\"steps\"];\n if (!Array.isArray(steps)) continue;\n for (const step of steps) {\n if (!step || typeof step !== \"object\") continue;\n const run = (step as Record<string, unknown>)[\"run\"];\n if (typeof run !== \"string\") continue;\n // A single run block may contain multiple lines\n for (const line of run.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n raw.push(trimmed);\n const category = categorizeCommand(trimmed);\n if (category !== null) {\n matched.push({ command: trimmed, category, ciFile: filePath });\n }\n }\n }\n }\n\n return { matched, raw };\n}\n\n/** Keys that are not job definitions in GitLab CI top-level. */\nconst GITLAB_RESERVED = new Set([\"stages\", \"variables\", \"include\", \"default\", \"workflow\", \"image\", \"services\", \"before_script\", \"after_script\", \"cache\", \"artifacts\"]);\n\n/**\n * Extract script commands from a GitLab CI YAML.\n * Traverses top-level job keys (skipping reserved keys), looks for script arrays.\n */\nfunction extractGitlabCiCommands(doc: unknown, filePath: string): { matched: CommandMatch[]; raw: string[] } {\n const matched: CommandMatch[] = [];\n const raw: string[] = [];\n\n if (!doc || typeof doc !== \"object\") return { matched, raw };\n\n for (const [key, job] of Object.entries(doc as Record<string, unknown>)) {\n if (GITLAB_RESERVED.has(key)) continue;\n if (!job || typeof job !== \"object\") continue;\n const script = (job as Record<string, unknown>)[\"script\"];\n const scripts = Array.isArray(script) ? script : typeof script === \"string\" ? [script] : [];\n for (const cmd of scripts) {\n if (typeof cmd !== \"string\") continue;\n const trimmed = cmd.trim();\n if (!trimmed) continue;\n raw.push(trimmed);\n const category = categorizeCommand(trimmed);\n if (category !== null) {\n matched.push({ command: trimmed, category, ciFile: filePath });\n }\n }\n }\n\n return { matched, raw };\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const ciExtractor: Extractor = {\n name: \"ci\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const ciPatterns = [\n \".github/workflows/*.yml\",\n \".github/workflows/*.yaml\",\n \".gitlab-ci.yml\",\n ];\n\n const ciFiles = await globby(ciPatterns, {\n cwd: ctx.projectPath,\n gitignore: false,\n followSymbolicLinks: false,\n absolute: false,\n });\n\n if (ciFiles.length === 0) return [];\n\n const entries: Entry[] = [];\n\n for (const relPath of ciFiles) {\n const absPath = join(ctx.projectPath, relPath);\n let raw: string;\n try {\n raw = await readFile(absPath, \"utf-8\");\n } catch {\n continue;\n }\n\n let doc: unknown;\n try {\n doc = yamlLoad(raw);\n } catch {\n // Malformed YAML: skip file, no throw\n continue;\n }\n\n const isGitlab = relPath.includes(\".gitlab-ci\");\n const { matched, raw: rawCmds } = isGitlab\n ? extractGitlabCiCommands(doc, relPath)\n : extractGithubActionsCommands(doc, relPath);\n\n // Store all raw commands in a single metadata entry per file\n if (rawCmds.length > 0 || matched.length > 0) {\n for (const { command, category } of matched) {\n entries.push({\n category,\n pattern: `CI command: ${command}`,\n confidence: 0.9,\n evidence: [{ file: relative(ctx.projectPath, absPath) || relPath, line: null }],\n metadata: { command, ciFile: relPath, rawCommands: rawCmds },\n });\n }\n }\n }\n\n return entries;\n },\n};\n","import { globby } from \"globby\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Directories and paths that are always excluded from file listings. */\nexport const ALWAYS_SKIP: readonly string[] = [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/generated/**\",\n \"**/.ez-search/**\",\n \"**/.ez-context/**\",\n \"**/.git/**\",\n];\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ListFilesOptions {\n /** The project root to search from. */\n cwd: string;\n /** File extensions to include (without dot). Defaults to ts, js, json, md. */\n extensions?: string[];\n /** Additional ignore patterns (glob syntax). */\n additionalIgnore?: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Utility\n// ---------------------------------------------------------------------------\n\n/**\n * List project files while respecting .gitignore (INTG-04) and always\n * skipping common generated/build directories.\n *\n * @returns Relative paths sorted alphabetically.\n */\nexport async function listProjectFiles(\n options: ListFilesOptions\n): Promise<string[]> {\n const {\n cwd,\n extensions = [\"ts\", \"js\", \"json\", \"md\"],\n additionalIgnore = [],\n } = options;\n\n const extPattern =\n extensions.length === 1\n ? `**/*.${extensions[0]}`\n : `**/*.{${extensions.join(\",\")}}`;\n\n const files = await globby(extPattern, {\n cwd,\n gitignore: true,\n ignore: [...ALWAYS_SKIP, ...additionalIgnore],\n followSymbolicLinks: false,\n absolute: false,\n });\n\n return files.sort();\n}\n","import { globby } from \"globby\";\nimport { ALWAYS_SKIP } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\ninterface TestPattern {\n glob: string;\n location: string;\n style: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Top-level test directory prefixes — files here are directory-based, not co-located. */\nconst TEST_DIR_PREFIXES = [\"test/\", \"tests/\", \"__tests__/\"];\n\nconst TEST_PATTERNS: TestPattern[] = [\n { glob: \"**/*.test.{ts,tsx,js,jsx}\", location: \"co-located\", style: \"*.test.ts style\" },\n { glob: \"**/*.spec.{ts,tsx,js,jsx}\", location: \"co-located\", style: \"*.spec.ts style\" },\n { glob: \"test/**/*.{ts,tsx,js,jsx}\", location: \"test/ directory\", style: \"test/ directory\" },\n { glob: \"tests/**/*.{ts,tsx,js,jsx}\", location: \"tests/ directory\", style: \"tests/ directory\" },\n { glob: \"__tests__/**/*.{ts,tsx,js,jsx}\", location: \"__tests__/ directory\", style: \"__tests__/ directory\" },\n];\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const projectStructureExtractor: Extractor = {\n name: \"project-structure\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const entries: Entry[] = [];\n\n for (const { glob, location, style } of TEST_PATTERNS) {\n let matches = await globby(glob, {\n cwd: ctx.projectPath,\n gitignore: true,\n ignore: [...ALWAYS_SKIP],\n followSymbolicLinks: false,\n absolute: false,\n });\n\n // For co-located patterns, exclude files that live in dedicated test directories\n if (location === \"co-located\") {\n matches = matches.filter(\n (f) => !TEST_DIR_PREFIXES.some((prefix) => f.startsWith(prefix))\n );\n }\n\n if (matches.length === 0) continue;\n\n const count = matches.length;\n const confidence = Math.min(0.95, 0.5 + count * 0.05);\n const evidenceFiles = matches.slice(0, 5);\n\n entries.push({\n category: \"testing\",\n pattern: `Test files in ${location} (${style})`,\n confidence,\n evidence: evidenceFiles.map((f) => ({ file: f, line: null })),\n metadata: {\n testFileCount: count,\n location,\n style,\n },\n });\n }\n\n return entries;\n },\n};\n","import { Project } from \"ts-morph\";\nimport { listProjectFiles } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\ntype CaseKind = \"camelCase\" | \"PascalCase\" | \"snake_case\" | \"UPPER_SNAKE_CASE\";\n\n// ---------------------------------------------------------------------------\n// Case classification\n// ---------------------------------------------------------------------------\n\n/**\n * Classify the naming convention of a single identifier.\n * Returns null for short names (< 4 chars) or unclassifiable names.\n */\nfunction classifyCase(name: string): CaseKind | null {\n if (name.length < 4) return null;\n\n if (/^[A-Z][A-Z0-9_]{3,}$/.test(name)) return \"UPPER_SNAKE_CASE\";\n if (/^[A-Z][a-zA-Z0-9]+$/.test(name)) return \"PascalCase\";\n if (/^[a-z][a-z0-9_]+$/.test(name) && name.includes(\"_\")) return \"snake_case\";\n if (/^[a-z][a-zA-Z0-9]+$/.test(name) && /[A-Z]/.test(name)) return \"camelCase\";\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const namingExtractor: Extractor = {\n name: \"naming\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const files = await listProjectFiles({\n cwd: ctx.projectPath,\n extensions: [\"ts\", \"tsx\", \"js\", \"jsx\"],\n });\n\n if (files.length === 0) return [];\n\n const maxFiles = ctx.options?.maxFilesForAst ?? 200;\n const filesToAnalyse = files.slice(0, maxFiles);\n\n const project = new Project({\n compilerOptions: { allowJs: true, noEmit: true },\n skipFileDependencyResolution: true,\n });\n\n // Absolute paths needed for ts-morph\n const absPaths = filesToAnalyse.map((f) => `${ctx.projectPath}/${f}`);\n project.addSourceFilesAtPaths(absPaths);\n\n // Track counts: { functions, variables, classes } -> { CaseKind -> count }\n const functions: Record<string, number> = {};\n const variables: Record<string, number> = {};\n const classes: Record<string, number> = {};\n const counts: Record<string, Record<string, number>> = { functions, variables, classes };\n\n for (const sf of project.getSourceFiles()) {\n for (const fn of sf.getFunctions()) {\n const name = fn.getName();\n if (!name) continue;\n const kind = classifyCase(name);\n if (kind) functions[kind] = (functions[kind] ?? 0) + 1;\n }\n\n for (const decl of sf.getVariableDeclarations()) {\n const name = decl.getName();\n const kind = classifyCase(name);\n if (kind) variables[kind] = (variables[kind] ?? 0) + 1;\n }\n\n for (const cls of sf.getClasses()) {\n const name = cls.getName();\n if (!name) continue;\n const kind = classifyCase(name);\n if (kind) classes[kind] = (classes[kind] ?? 0) + 1;\n }\n }\n\n const entries: Entry[] = [];\n\n for (const [entityType, caseCounts] of Object.entries(counts)) {\n const total = Object.values(caseCounts).reduce((a, b) => a + b, 0);\n if (total < 3) continue;\n\n // Find dominant case\n let dominant: CaseKind | null = null;\n let dominantCount = 0;\n for (const [kind, count] of Object.entries(caseCounts)) {\n if (count > dominantCount) {\n dominant = kind as CaseKind;\n dominantCount = count;\n }\n }\n\n if (!dominant) continue;\n\n const confidence = Math.min(0.95, dominantCount / total);\n if (confidence < 0.6) continue;\n\n entries.push({\n category: \"naming\",\n pattern: `${entityType} use ${dominant} naming`,\n confidence,\n evidence: [{ file: \"src/**/*.ts\", line: null }],\n metadata: {\n entityType,\n dominantCase: dominant,\n counts: caseCounts,\n sampleSize: total,\n },\n });\n }\n\n return entries;\n },\n};\n","import { join, dirname, resolve } from \"node:path\";\nimport { Project } from \"ts-morph\";\nimport { listProjectFiles } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\ntype SourceFile = ReturnType<InstanceType<typeof Project>[\"getSourceFiles\"]>[number];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Known path alias prefixes used across JS/TS ecosystems. */\nconst ALIAS_PREFIXES = [\"@/\", \"~/\", \"#/\", \"$lib/\"];\n\nfunction hasPathAlias(specifier: string): boolean {\n return ALIAS_PREFIXES.some((prefix) => specifier.startsWith(prefix));\n}\n\n/**\n * Check whether a source file looks like a barrel file:\n * - Has at least one export declaration\n * - Has no function, class, or variable declarations\n */\nfunction isBarrelFile(sourceFile: SourceFile): boolean {\n const hasExports = sourceFile.getExportDeclarations().length > 0;\n if (!hasExports) return false;\n const hasFunctions = sourceFile.getFunctions().length > 0;\n const hasClasses = sourceFile.getClasses().length > 0;\n const hasVars = sourceFile.getVariableDeclarations().length > 0;\n return !hasFunctions && !hasClasses && !hasVars;\n}\n\n/**\n * Build a set of absolute paths for barrel files (index.ts/js variants).\n * Since skipFileDependencyResolution prevents module resolution,\n * we identify barrel files upfront and match imports by path.\n */\nfunction buildBarrelFileSet(project: Project): Set<string> {\n const barrels = new Set<string>();\n for (const sf of project.getSourceFiles()) {\n const filePath = sf.getFilePath();\n const baseName = filePath.split(\"/\").pop() ?? \"\";\n // Only index files can be barrel files (index.ts, index.tsx, index.js, index.jsx)\n if (/^index\\.[tj]sx?$/.test(baseName) && isBarrelFile(sf)) {\n barrels.add(filePath);\n }\n }\n return barrels;\n}\n\n/** Resolve a relative import specifier to candidate absolute paths. */\nfunction resolveRelativeImport(importingFile: string, specifier: string): string[] {\n const dir = dirname(importingFile);\n const base = resolve(dir, specifier);\n const exts = [\".ts\", \".tsx\", \".js\", \".jsx\"];\n const candidates: string[] = [];\n // Direct file: ./foo -> ./foo.ts etc\n for (const ext of exts) candidates.push(base + ext);\n // Directory index: ./foo -> ./foo/index.ts etc\n for (const ext of exts) candidates.push(join(base, \"index\" + ext));\n return candidates;\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const importsExtractor: Extractor = {\n name: \"imports\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const files = await listProjectFiles({\n cwd: ctx.projectPath,\n extensions: [\"ts\", \"tsx\", \"js\", \"jsx\"],\n });\n\n if (files.length === 0) return [];\n\n const maxFiles = ctx.options?.maxFilesForAst ?? 200;\n const filesToAnalyse = files.slice(0, maxFiles);\n\n const project = new Project({\n compilerOptions: { allowJs: true, noEmit: true },\n skipFileDependencyResolution: true,\n });\n\n const absPaths = filesToAnalyse.map((f) => `${ctx.projectPath}/${f}`);\n project.addSourceFilesAtPaths(absPaths);\n\n // Pre-compute barrel file set for path-based matching\n const barrelFiles = buildBarrelFileSet(project);\n\n let relativeCount = 0;\n let externalCount = 0;\n let barrelCount = 0;\n let aliasCount = 0;\n\n for (const sf of project.getSourceFiles()) {\n for (const imp of sf.getImportDeclarations()) {\n const specifier = imp.getModuleSpecifierValue();\n\n if (imp.isModuleSpecifierRelative()) {\n relativeCount++;\n\n // Barrel detection via path heuristic\n const candidates = resolveRelativeImport(sf.getFilePath(), specifier);\n if (candidates.some((c) => barrelFiles.has(c))) {\n barrelCount++;\n }\n } else {\n externalCount++;\n\n if (hasPathAlias(specifier)) {\n aliasCount++;\n }\n }\n }\n }\n\n const totalImports = relativeCount + externalCount;\n if (totalImports === 0) return [];\n\n const entries: Entry[] = [];\n const evidence = [{ file: \"src/**/*.ts\", line: null }];\n\n // --- Import organization pattern ---\n const relRatio = relativeCount / totalImports;\n let orgPattern: string;\n if (relRatio >= 0.75) {\n orgPattern = \"Predominantly relative imports\";\n } else if (relRatio <= 0.25) {\n orgPattern = \"Predominantly external imports\";\n } else {\n orgPattern = \"Mix of relative and external imports\";\n }\n\n // Confidence scales with sample size (saturates at 100 imports)\n const sizeConfidence = Math.min(0.95, 0.5 + (totalImports / 100) * 0.45);\n\n entries.push({\n category: \"imports\",\n pattern: orgPattern,\n confidence: sizeConfidence,\n evidence,\n metadata: {\n relativeCount,\n externalCount,\n totalImports,\n relativeRatio: Math.round(relRatio * 100) / 100,\n },\n });\n\n // --- Barrel file usage ---\n if (barrelCount > 0) {\n const barrelRatio = barrelCount / relativeCount;\n entries.push({\n category: \"imports\",\n pattern: \"Uses barrel file (index) imports\",\n confidence: Math.min(0.95, 0.5 + barrelRatio * 0.45),\n evidence,\n metadata: { barrelCount, relativeCount },\n });\n }\n\n // --- Path alias usage ---\n if (aliasCount > 0) {\n entries.push({\n category: \"imports\",\n pattern: \"Uses path aliases (@/ prefix)\",\n confidence: 1.0,\n evidence,\n metadata: { aliasCount },\n });\n }\n\n return entries;\n },\n};\n","import { Project, SyntaxKind } from \"ts-morph\";\nimport { listProjectFiles } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const staticErrorHandlingExtractor: Extractor = {\n name: \"static-error-handling\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const files = await listProjectFiles({\n cwd: ctx.projectPath,\n extensions: [\"ts\", \"tsx\", \"js\", \"jsx\"],\n });\n\n if (files.length === 0) return [];\n\n const maxFiles = ctx.options?.maxFilesForAst ?? 200;\n const filesToAnalyse = files.slice(0, maxFiles);\n\n const project = new Project({\n compilerOptions: { allowJs: true, noEmit: true },\n skipFileDependencyResolution: true,\n });\n\n const absPaths = filesToAnalyse.map((f) => `${ctx.projectPath}/${f}`);\n project.addSourceFilesAtPaths(absPaths);\n\n let tryCatchFileCount = 0;\n let tryCatchTotalCount = 0;\n let customErrorClassCount = 0;\n const tryCatchEvidence: string[] = [];\n const customErrorEvidence: string[] = [];\n\n for (const sf of project.getSourceFiles()) {\n const relPath = sf.getFilePath().replace(ctx.projectPath + \"/\", \"\");\n\n const tryStatements = sf.getDescendantsOfKind(SyntaxKind.TryStatement);\n if (tryStatements.length > 0) {\n tryCatchFileCount++;\n tryCatchTotalCount += tryStatements.length;\n if (tryCatchEvidence.length < 5) tryCatchEvidence.push(relPath);\n }\n\n for (const cls of sf.getClasses()) {\n const heritage = cls.getExtends();\n if (heritage && /\\bError\\b/.test(heritage.getText())) {\n customErrorClassCount++;\n if (customErrorEvidence.length < 5) customErrorEvidence.push(relPath);\n }\n }\n }\n\n const entries: Entry[] = [];\n const totalFiles = filesToAnalyse.length;\n\n // Require at least 2 try/catch occurrences (across 1+ files) for a meaningful pattern\n if (tryCatchTotalCount >= 2) {\n // Confidence based on both file spread and occurrence density\n const fileSpread = tryCatchFileCount / totalFiles;\n const densityBoost = Math.min(0.2, tryCatchTotalCount * 0.05);\n const confidence = Math.min(0.95, 0.5 + fileSpread * 0.35 + densityBoost);\n entries.push({\n category: \"error_handling\",\n pattern: \"try/catch imperative error handling\",\n confidence,\n evidence: tryCatchEvidence.map((file) => ({ file, line: null })),\n metadata: { style: \"try-catch\", fileCount: tryCatchFileCount, totalCount: tryCatchTotalCount },\n });\n }\n\n if (customErrorClassCount >= 1) {\n const confidence = Math.min(0.95, 0.5 + (customErrorClassCount / totalFiles) * 0.45);\n entries.push({\n category: \"error_handling\",\n pattern: \"custom error class hierarchy\",\n confidence,\n evidence: customErrorEvidence.map((file) => ({ file, line: null })),\n metadata: { style: \"custom-error-class\", classCount: customErrorClassCount },\n });\n }\n\n return entries;\n },\n};\n","/**\n * ez-search bridge — thin adapter over @ez-corp/ez-search.\n *\n * This is the ONLY file that imports from @ez-corp/ez-search.\n * All other modules interact with ez-search via the EzSearchBridge interface.\n */\nimport { index, query, EzSearchError } from \"@ez-corp/ez-search\";\nimport { existsSync, rmSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Supporting types\n// ---------------------------------------------------------------------------\n\nexport interface SearchResult {\n file: string;\n chunk: string;\n score: number;\n}\n\n// ---------------------------------------------------------------------------\n// Bridge interface\n// ---------------------------------------------------------------------------\n\nexport interface EzSearchBridge {\n /**\n * Returns true if an .ez-search/ index exists for the project directory.\n */\n hasIndex(projectPath: string): Promise<boolean>;\n\n /**\n * Ensures an index exists: indexes the project if no index is present (EXTR-10).\n */\n ensureIndex(projectPath: string): Promise<void>;\n\n /**\n * Unconditionally re-indexes the project so search results reflect current file state.\n */\n refreshIndex(projectPath: string): Promise<void>;\n\n /**\n * Semantic (and hybrid) search over the indexed project.\n */\n search(query: string, options?: { k?: number }): Promise<SearchResult[]>;\n\n /**\n * Get an embedding vector for the given text.\n * NOTE: @ez-corp/ez-search does not expose a standalone embed API.\n * This method is reserved for future use or a companion embedder.\n */\n embed(text: string): Promise<number[]>;\n}\n\n// ---------------------------------------------------------------------------\n// Index corruption detection\n// ---------------------------------------------------------------------------\n\n/**\n * Check if a .ez-search/ index directory appears corrupt.\n *\n * Zvec (the native vector DB) will SIGABRT if it opens a collection with\n * corrupt .proxima files. Since SIGABRT kills the process before JS can\n * catch, we proactively detect corruption and wipe the index so it can\n * be rebuilt cleanly.\n *\n * Heuristic: a segment directory containing a .proxima file alongside a\n * zero-byte .ipc file indicates an interrupted or corrupt write.\n */\nexport function isIndexCorrupt(indexDir: string): boolean {\n let entries: string[];\n try {\n entries = readdirSync(indexDir);\n } catch {\n return false;\n }\n\n for (const entry of entries) {\n // Collection dirs are named like col-512, col-768\n if (!entry.startsWith(\"col-\")) continue;\n const colDir = join(indexDir, entry);\n\n let segments: string[];\n try {\n segments = readdirSync(colDir);\n } catch {\n continue;\n }\n\n for (const seg of segments) {\n const segPath = join(colDir, seg);\n try {\n if (!statSync(segPath).isDirectory()) continue;\n } catch {\n continue;\n }\n\n let files: string[];\n try {\n files = readdirSync(segPath);\n } catch {\n continue;\n }\n\n const hasProxima = files.some((f) => f.endsWith(\".proxima\"));\n const hasZeroByteIpc = files.some((f) => {\n if (!f.endsWith(\".ipc\")) return false;\n try {\n return statSync(join(segPath, f)).size === 0;\n } catch {\n return false;\n }\n });\n\n if (hasProxima && hasZeroByteIpc) return true;\n }\n }\n\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nclass EzSearchBridgeImpl implements EzSearchBridge {\n constructor(private readonly projectPath: string) {}\n\n async hasIndex(projectPath: string): Promise<boolean> {\n const indexDir = join(projectPath, \".ez-search\");\n return existsSync(indexDir);\n }\n\n async ensureIndex(projectPath: string): Promise<void> {\n if (await this.hasIndex(projectPath)) {\n return;\n }\n await index(projectPath);\n }\n\n async refreshIndex(projectPath: string): Promise<void> {\n const indexDir = join(projectPath, \".ez-search\");\n\n // Proactively wipe corrupt indexes to prevent Zvec native SIGABRT\n if (existsSync(indexDir) && isIndexCorrupt(indexDir)) {\n rmSync(indexDir, { recursive: true, force: true });\n }\n\n // Incremental: ez-search >=1.3.3 skips unchanged files (mtime+size+hash),\n // only re-embeds modified chunks, and auto-recovers from stale Zvec locks.\n try {\n await index(projectPath);\n } catch (err) {\n // Index may be partially corrupt in a way we didn't detect — wipe and retry once\n if (existsSync(indexDir)) {\n rmSync(indexDir, { recursive: true, force: true });\n await index(projectPath);\n } else {\n throw err;\n }\n }\n }\n\n async search(\n searchQuery: string,\n options: { k?: number } = {}\n ): Promise<SearchResult[]> {\n const { k = 10 } = options;\n\n let raw: Awaited<ReturnType<typeof query>>;\n try {\n raw = await query(searchQuery, {\n topK: k,\n projectDir: this.projectPath,\n autoIndex: false,\n });\n } catch (err: unknown) {\n if (err instanceof EzSearchError && err.code === \"NO_INDEX\") {\n return [];\n }\n throw err;\n }\n\n const results: SearchResult[] = [];\n\n for (const hit of raw.code) {\n results.push({ file: hit.file, chunk: hit.text, score: hit.score });\n }\n for (const hit of raw.text) {\n results.push({ file: hit.file, chunk: hit.text, score: hit.score });\n }\n\n results.sort((a, b) => b.score - a.score);\n return results.slice(0, k);\n }\n\n async embed(_text: string): Promise<number[]> {\n // @ez-corp/ez-search does not expose a standalone embed endpoint.\n // This is a planned capability that will be implemented when a\n // companion embedding API becomes available.\n throw new Error(\n \"embed() is not yet supported by the ez-search bridge. \" +\n \"Use search() for semantic retrieval.\"\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an EzSearchBridge bound to the given project directory.\n */\nexport async function createBridge(\n projectPath: string\n): Promise<EzSearchBridge> {\n return new EzSearchBridgeImpl(projectPath);\n}\n","import { createBridge } from \"../../core/ez-search-bridge.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\ntype ErrorStyle = \"try-catch\" | \"result-type\" | \"custom-error-class\" | \"error-boundary\";\n\ninterface PatternDef {\n style: ErrorStyle;\n pattern: string;\n test: (content: string) => boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Pattern definitions\n// ---------------------------------------------------------------------------\n\nconst PATTERNS: PatternDef[] = [\n {\n style: \"try-catch\",\n pattern: \"try/catch imperative error handling\",\n test: (content) => /\\btry\\s*\\{/.test(content) && /\\bcatch\\s*\\(/.test(content),\n },\n {\n style: \"result-type\",\n pattern: \"Result/Either functional error handling\",\n test: (content) =>\n /\\bResult<|\\bOk\\(|\\bErr\\(|\\bisOk\\b|\\bisErr\\b|\\bneverthrow\\b/.test(content),\n },\n {\n style: \"custom-error-class\",\n pattern: \"custom error class hierarchy\",\n test: (content) => /class\\s+\\w+Error\\b|\\bnew\\s+\\w+Error\\(/.test(content),\n },\n {\n style: \"error-boundary\",\n pattern: \"React error boundary components\",\n test: (content) => /\\bErrorBoundary\\b|\\bcomponentDidCatch\\b/.test(content),\n },\n];\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const errorHandlingExtractor: Extractor = {\n name: \"error-handling\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const bridge = await createBridge(ctx.projectPath);\n\n if (!(await bridge.hasIndex(ctx.projectPath))) {\n return [];\n }\n\n // Issue targeted search queries\n const [general, resultType, customErrors] = await Promise.all([\n bridge.search(\"error handling try catch throw exception\", { k: 30 }),\n bridge.search(\"Result Ok Err return error value\", { k: 20 }),\n bridge.search(\"custom error class extends Error\", { k: 20 }),\n ]);\n\n // Merge and deduplicate chunks by file (concatenate text for same file)\n const fileContentMap = new Map<string, string>();\n for (const result of [...general, ...resultType, ...customErrors]) {\n const existing = fileContentMap.get(result.file) ?? \"\";\n fileContentMap.set(result.file, existing + \"\\n\" + result.chunk);\n }\n\n const totalUniqueFiles = fileContentMap.size;\n if (totalUniqueFiles === 0) return [];\n\n const entries: Entry[] = [];\n\n for (const patternDef of PATTERNS) {\n const matchingFiles: string[] = [];\n\n for (const [file, content] of fileContentMap) {\n if (patternDef.test(content)) {\n matchingFiles.push(file);\n }\n }\n\n // Require at least 2 distinct files\n if (matchingFiles.length < 2) continue;\n\n const confidence = Math.min(0.95, 0.5 + (matchingFiles.length / totalUniqueFiles) * 0.45);\n\n entries.push({\n category: \"error_handling\",\n pattern: patternDef.pattern,\n confidence,\n evidence: matchingFiles.slice(0, 5).map((file) => ({ file, line: null })),\n metadata: {\n style: patternDef.style,\n fileCount: matchingFiles.length,\n },\n });\n }\n\n return entries;\n },\n};\n","import { createBridge } from \"../../core/ez-search-bridge.js\";\nimport { listProjectFiles } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\ntype ArchPattern = \"MVC\" | \"feature-based\" | \"layer-based\";\n\n// ---------------------------------------------------------------------------\n// Directory pattern detection\n// ---------------------------------------------------------------------------\n\n/** Get directory paths under src/ (or project root if no src/). */\nfunction extractSourceDirs(files: string[]): Set<string> {\n const dirs = new Set<string>();\n for (const f of files) {\n const parts = f.split(\"/\");\n if (parts.length < 2) continue;\n\n if (parts[0] === \"src\" && parts.length >= 3) {\n // Under src/ -- use \"src/subdir\" as the dir\n dirs.add(`src/${parts[1]}`);\n } else if (parts[0] !== \"src\") {\n // Project root level\n dirs.add(parts[0]!);\n }\n }\n return dirs;\n}\n\n/** Normalise a directory name to lower case for matching. */\nfunction normalise(dir: string): string {\n return dir.split(\"/\").pop()!.toLowerCase();\n}\n\n/** Detect MVC: >= 2 of models/, views/, controllers/, routes/ */\nfunction detectMVC(sourceDirs: Set<string>): string[] {\n const mvc = [\"model\", \"models\", \"view\", \"views\", \"controller\", \"controllers\", \"route\", \"routes\"];\n const found: string[] = [];\n for (const dir of sourceDirs) {\n if (mvc.includes(normalise(dir))) found.push(dir);\n }\n // Deduplicate by canonical name (model/models both count as one)\n const canonical = new Set(found.map((d) => normalise(d).replace(/s$/, \"\")));\n return canonical.size >= 2 ? found : [];\n}\n\n/** Detect feature-based: files under features/, modules/, pages/ with >= 5 files total. */\nfunction detectFeatureBased(files: string[]): string[] {\n const featurePattern = /\\/(features?|modules?|pages?)\\//i;\n const featureDirs = new Set<string>();\n let count = 0;\n for (const f of files) {\n if (featurePattern.test(f)) {\n count++;\n // Extract the feature root dir (e.g. \"src/features\" or \"features\")\n const match = f.match(/^(.*?\\/(features?|modules?|pages?))\\//i);\n if (match) featureDirs.add(match[1]!);\n }\n }\n return count >= 5 ? Array.from(featureDirs) : [];\n}\n\n/** Detect layer-based architecture (DDD / hexagonal / clean arch). */\nfunction detectLayerBased(sourceDirs: Set<string>): string[] {\n const layerPatterns = [\n // DDD\n \"domain\", \"application\", \"infrastructure\",\n // Hexagonal\n \"service\", \"services\", \"repository\", \"repositories\", \"handler\", \"handlers\", \"usecase\", \"usecases\",\n // Clean arch\n \"core\", \"data\", \"presentation\",\n ];\n const found: string[] = [];\n for (const dir of sourceDirs) {\n if (layerPatterns.includes(normalise(dir))) found.push(dir);\n }\n // Need >= 2 distinct layer dirs\n const canonical = new Set(found.map((d) => normalise(d)));\n return canonical.size >= 2 ? found : [];\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const architectureExtractor: Extractor = {\n name: \"architecture\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n // Signal 1: Directory structure scan (deterministic)\n const files = await listProjectFiles({\n cwd: ctx.projectPath,\n extensions: [\"ts\", \"js\", \"tsx\", \"jsx\", \"py\", \"rb\", \"go\", \"rs\"],\n });\n\n const sourceDirs = extractSourceDirs(files);\n\n // Try each pattern\n const mvcDirs = detectMVC(sourceDirs);\n const featureDirs = detectFeatureBased(files);\n const layerDirs = detectLayerBased(sourceDirs);\n\n let detectedPattern: ArchPattern | null = null;\n let detectedLayers: string[] = [];\n\n if (mvcDirs.length > 0) {\n detectedPattern = \"MVC\";\n detectedLayers = mvcDirs;\n } else if (featureDirs.length > 0) {\n detectedPattern = \"feature-based\";\n detectedLayers = featureDirs;\n } else if (layerDirs.length > 0) {\n detectedPattern = \"layer-based\";\n detectedLayers = layerDirs;\n }\n\n if (!detectedPattern) return [];\n\n // Signal 2: Semantic search confirmation (optional -- adds evidence and boosts confidence)\n const bridge = await createBridge(ctx.projectPath);\n const hasIdx = await bridge.hasIndex(ctx.projectPath);\n\n let confidence = 0.7; // directory-only\n const evidence: { file: string; line: null }[] = detectedLayers\n .slice(0, 3)\n .map((dir) => ({ file: dir, line: null }));\n\n if (hasIdx) {\n const searchResults = await bridge.search(\n \"model view controller route handler service repository\",\n { k: 20 }\n );\n if (searchResults.length > 0) {\n confidence = 0.85;\n for (const r of searchResults.slice(0, 2)) {\n evidence.push({ file: r.file, line: null });\n }\n }\n }\n\n // Deduplicate evidence by file\n const seen = new Set<string>();\n const deduped = evidence.filter(({ file }) => {\n if (seen.has(file)) return false;\n seen.add(file);\n return true;\n });\n\n return [\n {\n category: \"architecture\",\n pattern: patternLabel(detectedPattern),\n confidence,\n evidence: deduped.slice(0, 5),\n metadata: {\n architecturePattern: detectedPattern,\n layers: detectedLayers,\n },\n },\n ];\n },\n};\n\nfunction patternLabel(p: ArchPattern): string {\n switch (p) {\n case \"MVC\":\n return \"MVC architecture pattern\";\n case \"feature-based\":\n return \"Feature-based architecture\";\n case \"layer-based\":\n return \"Layer-based architecture\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers exposed for testing\n// ---------------------------------------------------------------------------\n\nexport { extractSourceDirs, detectMVC, detectFeatureBased, detectLayerBased };\n","import { createRegistry } from \"./registry.js\";\nimport { ConventionRegistrySchema } from \"./schema.js\";\nimport type { ConventionEntry, ConventionRegistry, EvidenceRef } from \"./schema.js\";\nimport { runExtractors } from \"../extractors/index.js\";\nimport type { ExtractorOptions } from \"../extractors/types.js\";\nimport { packageJsonExtractor } from \"../extractors/static/package-json.js\";\nimport { lockfileExtractor } from \"../extractors/static/lockfile.js\";\nimport { tsconfigExtractor } from \"../extractors/static/tsconfig.js\";\nimport { goModExtractor } from \"../extractors/static/go-mod.js\";\nimport { cargoTomlExtractor } from \"../extractors/static/cargo-toml.js\";\nimport { ciExtractor } from \"../extractors/static/ci.js\";\nimport { projectStructureExtractor } from \"../extractors/static/project-structure.js\";\nimport { namingExtractor } from \"../extractors/code/naming.js\";\nimport { importsExtractor } from \"../extractors/code/imports.js\";\nimport { staticErrorHandlingExtractor } from \"../extractors/code/error-handling.js\";\nimport { errorHandlingExtractor } from \"../extractors/semantic/error-handling.js\";\nimport { architectureExtractor } from \"../extractors/semantic/architecture.js\";\n\n// ---------------------------------------------------------------------------\n// Extractor registry (ordered by confidence priority for StackInfo population)\n// ---------------------------------------------------------------------------\n\nconst ALL_EXTRACTORS = [\n packageJsonExtractor,\n lockfileExtractor,\n tsconfigExtractor,\n goModExtractor,\n cargoTomlExtractor,\n ciExtractor,\n projectStructureExtractor,\n namingExtractor,\n importsExtractor,\n staticErrorHandlingExtractor,\n errorHandlingExtractor,\n architectureExtractor,\n];\n\n// ---------------------------------------------------------------------------\n// Deduplication helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Deduplicate evidence by file+line combination.\n */\nfunction deduplicateEvidence(evidence: EvidenceRef[]): EvidenceRef[] {\n const seen = new Set<string>();\n return evidence.filter((e) => {\n const key = `${e.file}:${e.line ?? \"null\"}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/**\n * Deduplicate conventions by category+pattern.\n * For duplicates, keep the one with higher confidence.\n * Merge evidence arrays from duplicates.\n */\nfunction deduplicateConventions(\n conventions: ConventionEntry[]\n): ConventionEntry[] {\n const grouped = new Map<string, ConventionEntry>();\n\n for (const entry of conventions) {\n const key = `${entry.category}:${entry.pattern}`;\n const existing = grouped.get(key);\n\n if (!existing) {\n grouped.set(key, entry);\n } else {\n // Keep higher confidence, merge evidence\n const winner: ConventionEntry =\n entry.confidence > existing.confidence ? entry : existing;\n const mergedEvidence = deduplicateEvidence([\n ...existing.evidence,\n ...entry.evidence,\n ]);\n grouped.set(key, { ...winner, evidence: mergedEvidence });\n }\n }\n\n return Array.from(grouped.values());\n}\n\n// ---------------------------------------------------------------------------\n// StackInfo population helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Populate StackInfo from extracted conventions via a post-extraction pass.\n * Conventions are processed in array order; first match wins for each field.\n */\nfunction populateStackInfo(registry: ConventionRegistry): ConventionRegistry {\n const stack = { ...registry.stack };\n\n for (const entry of registry.conventions) {\n const meta = entry.metadata ?? {};\n\n if (entry.category === \"stack\") {\n if (!stack.language || stack.language === \"unknown\") {\n if (typeof meta.language === \"string\") {\n stack.language = meta.language;\n }\n }\n if (!stack.framework && typeof meta.framework === \"string\") {\n stack.framework = meta.framework;\n }\n if (!stack.testRunner && typeof meta.testRunner === \"string\") {\n stack.testRunner = meta.testRunner;\n }\n if (!stack.packageManager && typeof meta.packageManager === \"string\") {\n stack.packageManager = meta.packageManager;\n }\n if (!stack.buildTool) {\n // Detect buildTool from scripts metadata\n if (typeof meta.buildTool === \"string\") {\n stack.buildTool = meta.buildTool;\n } else if (\n typeof meta.scriptName === \"string\" &&\n meta.scriptName === \"build\" &&\n typeof meta.command === \"string\"\n ) {\n // Extract the build tool from the build script command (first word)\n stack.buildTool = (meta.command as string).split(\" \")[0];\n }\n }\n }\n\n if (entry.category === \"testing\") {\n if (!stack.testRunner && typeof meta.testRunner === \"string\") {\n stack.testRunner = meta.testRunner;\n }\n }\n }\n\n return { ...registry, stack };\n}\n\n// ---------------------------------------------------------------------------\n// ArchitectureInfo population helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Populate ArchitectureInfo from extracted conventions via a post-extraction pass.\n * First architecture convention with the relevant metadata wins.\n */\nfunction populateArchitectureInfo(\n registry: ConventionRegistry\n): ConventionRegistry {\n const arch = { ...registry.architecture };\n\n for (const entry of registry.conventions) {\n if (entry.category === \"architecture\") {\n if (\n !arch.pattern &&\n typeof entry.metadata?.architecturePattern === \"string\"\n ) {\n arch.pattern = entry.metadata.architecturePattern;\n }\n if (\n arch.layers.length === 0 &&\n Array.isArray(entry.metadata?.layers)\n ) {\n arch.layers = entry.metadata.layers as string[];\n }\n }\n }\n\n return { ...registry, architecture: arch };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Run the full extraction pipeline against the given project path.\n *\n * 1. Runs all extractors in parallel (Promise.allSettled)\n * 2. Deduplicates conventions by category+pattern (higher confidence wins)\n * 3. Populates StackInfo from convention metadata\n * 4. Populates ArchitectureInfo from convention metadata\n * 5. Validates and returns the final ConventionRegistry\n */\nexport async function extractConventions(\n projectPath: string,\n options?: ExtractorOptions\n): Promise<ConventionRegistry> {\n const ctx = { projectPath, options };\n const emptyRegistry = createRegistry(projectPath);\n\n // Run all extractors\n const populated = await runExtractors(ALL_EXTRACTORS, ctx, emptyRegistry);\n\n // Deduplicate conventions\n const deduplicated: ConventionRegistry = {\n ...populated,\n conventions: deduplicateConventions(populated.conventions),\n };\n\n // Populate StackInfo from convention metadata\n const withStack = populateStackInfo(deduplicated);\n\n // Populate ArchitectureInfo from convention metadata\n const withArch = populateArchitectureInfo(withStack);\n\n // Validate and return\n return ConventionRegistrySchema.parse(withArch);\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\n\nexport const MARKER_START = \"<!-- ez-context:start -->\";\nexport const MARKER_END = \"<!-- ez-context:end -->\";\n\n/**\n * Write content into the marker section of filePath.\n *\n * Three paths:\n * 1. File does not exist: creates it with markers wrapping content.\n * 2. File exists, no markers (or only one marker): appends the section at the end.\n * 3. File exists with both markers: splices new content between existing markers,\n * preserving everything outside.\n */\nexport async function writeWithMarkers(\n filePath: string,\n content: string\n): Promise<void> {\n const wrapped = `${MARKER_START}\\n${content}\\n${MARKER_END}`;\n\n if (!existsSync(filePath)) {\n await writeFile(filePath, wrapped + \"\\n\", \"utf-8\");\n return;\n }\n\n const existing = await readFile(filePath, \"utf-8\");\n const startIdx = existing.indexOf(MARKER_START);\n const endIdx = existing.indexOf(MARKER_END);\n\n // Treat missing or unpaired marker as \"no markers\" — append section\n if (startIdx === -1 || endIdx === -1) {\n const separator = existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n await writeFile(filePath, existing + separator + wrapped + \"\\n\", \"utf-8\");\n return;\n }\n\n const before = existing.slice(0, startIdx);\n const after = existing.slice(endIdx + MARKER_END.length);\n await writeFile(filePath, before + wrapped + after, \"utf-8\");\n}\n","// Shared rendering helpers for all emitter modules\nimport type { ConventionRegistry, ConventionEntry } from \"../core/schema.js\";\n\n/**\n * Extract script commands from a filtered list of convention entries.\n * Shared by agents-md and renderConventionsBody.\n */\nexport function extractCommands(\n filtered: ConventionEntry[]\n): Array<{ scriptName: string; command: string }> {\n const commands: Array<{ scriptName: string; command: string }> = [];\n for (const entry of filtered) {\n const meta = entry.metadata;\n if (\n meta &&\n typeof meta[\"command\"] === \"string\" &&\n typeof meta[\"scriptName\"] === \"string\"\n ) {\n commands.push({\n scriptName: meta[\"scriptName\"] as string,\n command: meta[\"command\"] as string,\n });\n }\n }\n return commands;\n}\n\n/**\n * Check if a stack-category convention should appear in the Conventions section.\n * Stack entries that map to StackInfo fields (language, framework, etc.) are already\n * shown in the Stack section. Others (e.g. \"TypeScript strict mode\") are convention-worthy.\n */\nfunction isStackConventionWorthy(\n entry: ConventionEntry,\n stack: ConventionRegistry[\"stack\"]\n): boolean {\n if (entry.category !== \"stack\") return false;\n const meta = entry.metadata ?? {};\n // Already represented in Stack section via StackInfo fields\n if (typeof meta[\"language\"] === \"string\" && stack.language !== \"unknown\") return false;\n if (typeof meta[\"framework\"] === \"string\" && stack.framework) return false;\n if (typeof meta[\"buildTool\"] === \"string\" && stack.buildTool) return false;\n if (typeof meta[\"packageManager\"] === \"string\" && stack.packageManager) return false;\n // Script entries are shown in Commands section\n if (typeof meta[\"scriptName\"] === \"string\") return false;\n // Everything else (strict mode, compiler options, etc.) is convention-worthy\n return true;\n}\n\n/**\n * Check if a convention entry is redundant with the Stack or Commands sections.\n * - \"Test runner: X\" in testing category duplicates Stack > Test Runner\n * - Entries with metadata.scriptName duplicate the Commands section\n */\nexport function isRedundantConvention(entry: ConventionEntry): boolean {\n if (entry.category === \"testing\" && entry.pattern.startsWith(\"Test runner:\")) {\n return true;\n }\n if (entry.metadata && typeof entry.metadata[\"scriptName\"] === \"string\") {\n return true;\n }\n return false;\n}\n\n/**\n * Render conventions body as markdown lines.\n * Used by cursor-mdc, skill-md, rulesync-md, ruler-md, copilot-md.\n */\nexport function renderConventionsBody(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string[] {\n const filtered = registry.conventions.filter(\n (c) => c.confidence >= confidenceThreshold\n );\n\n const lines: string[] = [];\n\n // Stack section\n const s = registry.stack;\n const hasStack =\n s.language !== \"unknown\" ||\n Boolean(s.framework) ||\n Boolean(s.buildTool) ||\n Boolean(s.packageManager) ||\n Boolean(s.testRunner);\n\n if (hasStack) {\n lines.push(\"## Stack\");\n if (s.language !== \"unknown\") lines.push(`- Language: ${s.language}`);\n if (s.framework) lines.push(`- Framework: ${s.framework}`);\n if (s.buildTool) lines.push(`- Build: ${s.buildTool}`);\n if (s.packageManager) lines.push(`- Package Manager: ${s.packageManager}`);\n if (s.testRunner) lines.push(`- Test Runner: ${s.testRunner}`);\n lines.push(\"\");\n }\n\n // Architecture section\n const a = registry.architecture;\n const hasArchitecture =\n Boolean(a.pattern) || (a.layers?.length ?? 0) > 0;\n\n if (hasArchitecture) {\n lines.push(\"## Architecture\");\n if (a.pattern) lines.push(`- Pattern: ${a.pattern}`);\n if (a.layers && a.layers.length > 0) {\n lines.push(`- Layers: ${a.layers.join(\", \")}`);\n }\n lines.push(\"\");\n }\n\n // Conventions section — group by category, exclude architecture and redundant entries.\n // Stack entries without scriptName are kept (e.g. \"TypeScript strict mode enabled\").\n const categoryMap = new Map<string, string[]>();\n for (const entry of filtered) {\n if (entry.category === \"architecture\") continue;\n if (entry.category === \"stack\" && !isStackConventionWorthy(entry, s)) continue;\n if (isRedundantConvention(entry)) continue;\n const list = categoryMap.get(entry.category) ?? [];\n list.push(entry.pattern);\n categoryMap.set(entry.category, list);\n }\n\n if (categoryMap.size > 0) {\n lines.push(\"## Conventions\");\n for (const [category, patterns] of categoryMap) {\n for (const pattern of patterns) {\n lines.push(`- **${category}**: ${pattern}`);\n }\n }\n lines.push(\"\");\n }\n\n // Commands section — from conventions with metadata.command + metadata.scriptName\n const commands = extractCommands(filtered);\n\n if (commands.length > 0) {\n lines.push(\"## Commands\");\n for (const cmd of commands) {\n lines.push(`- \\`${cmd.scriptName}\\`: \\`${cmd.command}\\``);\n }\n lines.push(\"\");\n }\n\n return lines;\n}\n","import type { ConventionRegistry, ConventionEntry } from \"../core/schema.js\";\nimport { isRedundantConvention } from \"./render-helpers.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ConventionGroup {\n category: string;\n entries: ConventionEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Data prep\n// ---------------------------------------------------------------------------\n\nfunction prepData(registry: ConventionRegistry, confidenceThreshold: number) {\n const filtered = registry.conventions.filter(\n (c) => c.confidence >= confidenceThreshold\n );\n\n // Group by category, excluding architecture and entries redundant with Stack/Commands.\n // Stack entries not covered by StackInfo fields (e.g. \"TypeScript strict mode\") are kept.\n const categoryMap = new Map<string, ConventionEntry[]>();\n for (const entry of filtered) {\n if (entry.category === \"architecture\") continue;\n if (entry.category === \"stack\") {\n const meta = entry.metadata ?? {};\n // Skip entries already rendered in the Stack section\n if (typeof meta[\"language\"] === \"string\") continue;\n if (typeof meta[\"framework\"] === \"string\") continue;\n if (typeof meta[\"buildTool\"] === \"string\") continue;\n if (typeof meta[\"packageManager\"] === \"string\") continue;\n if (typeof meta[\"scriptName\"] === \"string\") continue;\n }\n if (isRedundantConvention(entry)) continue;\n const list = categoryMap.get(entry.category) ?? [];\n list.push(entry);\n categoryMap.set(entry.category, list);\n }\n\n const conventionGroups: ConventionGroup[] = [];\n for (const [category, entries] of categoryMap) {\n conventionGroups.push({ category, entries });\n }\n\n const hasStack =\n registry.stack.language !== \"unknown\" ||\n Boolean(registry.stack.framework) ||\n Boolean(registry.stack.buildTool) ||\n Boolean(registry.stack.packageManager) ||\n Boolean(registry.stack.testRunner);\n\n const hasArchitecture =\n Boolean(registry.architecture.pattern) ||\n (registry.architecture.layers?.length ?? 0) > 0;\n\n return {\n stack: registry.stack,\n architecture: registry.architecture,\n conventionGroups,\n hasStack,\n hasArchitecture,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Renderer\n// ---------------------------------------------------------------------------\n\nexport function renderClaudeMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const data = prepData(registry, confidenceThreshold);\n const lines: string[] = [];\n\n lines.push(\"# Project Context\");\n\n // Stack section\n if (data.hasStack) {\n lines.push(\"\");\n lines.push(\"## Stack\");\n if (data.stack.language !== \"unknown\") {\n lines.push(`- Language: ${data.stack.language}`);\n }\n if (data.stack.framework) lines.push(`- Framework: ${data.stack.framework}`);\n if (data.stack.buildTool) lines.push(`- Build: ${data.stack.buildTool}`);\n if (data.stack.packageManager) lines.push(`- Package Manager: ${data.stack.packageManager}`);\n if (data.stack.testRunner) lines.push(`- Test Runner: ${data.stack.testRunner}`);\n }\n\n // Conventions section\n if (data.conventionGroups.length > 0) {\n lines.push(\"\");\n lines.push(\"## Conventions\");\n for (const group of data.conventionGroups) {\n for (const entry of group.entries) {\n lines.push(`- **${group.category}**: ${entry.pattern}`);\n }\n }\n }\n\n // Architecture section\n if (data.hasArchitecture) {\n lines.push(\"\");\n lines.push(\"## Architecture\");\n if (data.architecture.pattern) {\n lines.push(`- Pattern: ${data.architecture.pattern}`);\n }\n if (data.architecture.layers && data.architecture.layers.length > 0) {\n lines.push(`- Layers: ${data.architecture.layers.join(\", \")}`);\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","import type { ConventionRegistry, ConventionEntry } from \"../core/schema.js\";\nimport { extractCommands, isRedundantConvention } from \"./render-helpers.js\";\n\n// ---------------------------------------------------------------------------\n// Data prep\n// ---------------------------------------------------------------------------\n\nfunction prepData(registry: ConventionRegistry, confidenceThreshold: number) {\n const filtered = registry.conventions.filter(\n (c) => c.confidence >= confidenceThreshold\n );\n\n const commands = extractCommands(filtered);\n\n const byCategory = (cat: string): ConventionEntry[] =>\n filtered.filter((c) => c.category === cat);\n\n const testingConventions = byCategory(\"testing\");\n const namingConventions = byCategory(\"naming\");\n const importConventions = byCategory(\"imports\");\n const gitConventions = filtered.filter(\n (c) =>\n c.pattern.toLowerCase().includes(\"git\") ||\n (c.category === \"other\" && c.pattern.toLowerCase().includes(\"commit\"))\n );\n\n const hasTesting =\n Boolean(registry.stack.testRunner) || testingConventions.length > 0;\n\n const hasProjectStructure =\n Boolean(registry.architecture.pattern) ||\n (registry.architecture.layers?.length ?? 0) > 0;\n\n const hasCodeStyle =\n namingConventions.length > 0 || importConventions.length > 0;\n\n return {\n commands,\n testRunner: registry.stack.testRunner ?? null,\n testingConventions,\n namingConventions,\n importConventions,\n gitConventions,\n architecture: registry.architecture,\n hasTesting,\n hasProjectStructure,\n hasCodeStyle,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Renderer\n// ---------------------------------------------------------------------------\n\nexport function renderAgentsMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const data = prepData(registry, confidenceThreshold);\n const lines: string[] = [];\n\n lines.push(\"# AGENTS.md\");\n\n // Commands section\n if (data.commands.length > 0) {\n lines.push(\"\");\n lines.push(\"## Commands\");\n for (const cmd of data.commands) {\n lines.push(`- \\`${cmd.scriptName}\\`: \\`${cmd.command}\\``);\n }\n }\n\n // Testing section\n if (data.hasTesting) {\n lines.push(\"\");\n lines.push(\"## Testing\");\n if (data.testRunner) lines.push(`- Test runner: ${data.testRunner}`);\n for (const entry of data.testingConventions) {\n if (isRedundantConvention(entry)) continue;\n lines.push(`- ${entry.pattern}`);\n }\n }\n\n // Project Structure section\n if (data.hasProjectStructure) {\n lines.push(\"\");\n lines.push(\"## Project Structure\");\n if (data.architecture.pattern) {\n lines.push(`- Architecture: ${data.architecture.pattern}`);\n }\n if (data.architecture.layers && data.architecture.layers.length > 0) {\n lines.push(`- Layers: ${data.architecture.layers.join(\", \")}`);\n }\n }\n\n // Code Style section\n if (data.hasCodeStyle) {\n lines.push(\"\");\n lines.push(\"## Code Style\");\n for (const entry of data.namingConventions) {\n lines.push(`- **naming**: ${entry.pattern}`);\n }\n for (const entry of data.importConventions) {\n lines.push(`- **imports**: ${entry.pattern}`);\n }\n }\n\n // Git Workflow section (optional)\n if (data.gitConventions.length > 0) {\n lines.push(\"\");\n lines.push(\"## Git Workflow\");\n for (const entry of data.gitConventions) {\n lines.push(`- ${entry.pattern}`);\n }\n }\n\n // Boundaries section (always present)\n lines.push(\"\");\n lines.push(\"## Boundaries\");\n lines.push(\"- Do not modify auto-generated sections between ez-context markers\");\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","import yaml from \"js-yaml\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a Cursor MDC rule file (.cursor/rules/ez-context.mdc).\n *\n * Format: YAML frontmatter + markdown body.\n * - description: shown in Cursor UI\n * - globs: empty string (not null/omitted) per Cursor docs\n * - alwaysApply: true ensures conventions are always in context\n */\nexport function renderCursorMdc(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const frontmatter = yaml\n .dump({\n description: \"Project conventions extracted by ez-context\",\n globs: \"\",\n alwaysApply: true,\n })\n .trimEnd();\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line added by renderConventionsBody\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n const body = bodyLines.join(\"\\n\");\n\n return `---\\n${frontmatter}\\n---\\n\\n${body}\\n`;\n}\n","import type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a GitHub Copilot instructions file (.github/copilot-instructions.md).\n *\n * Format: Plain markdown, no YAML frontmatter.\n * GitHub Copilot reads the entire file; HTML comment markers are used\n * for idempotent updates via writeWithMarkers.\n */\nexport function renderCopilotMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const lines: string[] = [];\n\n lines.push(\"<!-- Generated by ez-context. Do not edit between markers. -->\");\n lines.push(\"\");\n lines.push(\"# Copilot Instructions\");\n lines.push(\"\");\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n lines.push(...bodyLines);\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","import yaml from \"js-yaml\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a SKILL.md module file (.skills/ez-context/SKILL.md).\n *\n * Format: YAML frontmatter + markdown body.\n * - name: must match directory name (ez-context)\n * - description: max 1024 chars, describes what AND when to use\n * Body stays under 5000 tokens (~3750 words) as per SKILL.md spec.\n */\nexport function renderSkillMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const description =\n \"Project conventions and coding standards for this codebase. \" +\n \"Use when writing new code, reviewing patterns, or understanding project architecture.\";\n\n const frontmatter = yaml\n .dump({\n name: \"ez-context\",\n description,\n })\n .trimEnd();\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n const body = bodyLines.join(\"\\n\");\n\n return `---\\n${frontmatter}\\n---\\n\\n${body}\\n`;\n}\n","import yaml from \"js-yaml\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a Rulesync rule file (.rulesync/rules/ez-context.md).\n *\n * Format: YAML frontmatter + markdown body.\n * - targets: specifies which AI tools receive this rule\n * ez-context writes INTO .rulesync/rules/; Rulesync distributes to tools.\n */\nexport function renderRulesyncMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const frontmatter = yaml\n .dump({\n description: \"Project conventions extracted by ez-context\",\n targets: [\"cursor\", \"copilot\", \"windsurf\"],\n })\n .trimEnd();\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n const body = bodyLines.join(\"\\n\");\n\n return `---\\n${frontmatter}\\n---\\n\\n${body}\\n`;\n}\n","import type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a Ruler rule file (.ruler/ez-context.md).\n *\n * Format: Plain markdown, no YAML frontmatter.\n * Ruler recursively discovers all .md files in .ruler/ and distributes them.\n * ez-context writes a single .ruler/ez-context.md as an additive conventions file.\n */\nexport function renderRulerMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const lines: string[] = [];\n\n lines.push(\"# Project Conventions (ez-context)\");\n lines.push(\"\");\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n lines.push(...bodyLines);\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","import path from \"node:path\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport type { EmitOptions, EmitResult, OutputFormat } from \"./types.js\";\nimport { writeWithMarkers } from \"./writer.js\";\nimport { renderClaudeMd } from \"./claude-md.js\";\nimport { renderAgentsMd } from \"./agents-md.js\";\nimport { renderCursorMdc } from \"./cursor-mdc.js\";\nimport { renderCopilotMd } from \"./copilot-md.js\";\nimport { renderSkillMd } from \"./skill-md.js\";\nimport { renderRulesyncMd } from \"./rulesync-md.js\";\nimport { renderRulerMd } from \"./ruler-md.js\";\n\nexport { renderClaudeMd } from \"./claude-md.js\";\nexport { renderAgentsMd } from \"./agents-md.js\";\nexport { renderCursorMdc } from \"./cursor-mdc.js\";\nexport { renderCopilotMd } from \"./copilot-md.js\";\nexport { renderSkillMd } from \"./skill-md.js\";\nexport { renderRulesyncMd } from \"./rulesync-md.js\";\nexport { renderRulerMd } from \"./ruler-md.js\";\nexport type { OutputFormat } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Format emitter registry\n// ---------------------------------------------------------------------------\n\ntype WriteStrategy = \"markers\" | \"direct\";\n\ninterface FormatEmitterEntry {\n render: (registry: ConventionRegistry, threshold: number) => string;\n filename: string;\n strategy: WriteStrategy;\n}\n\nexport const FORMAT_EMITTER_MAP: Record<OutputFormat, FormatEmitterEntry> = {\n claude: {\n render: renderClaudeMd,\n filename: \"CLAUDE.md\",\n strategy: \"markers\",\n },\n agents: {\n render: renderAgentsMd,\n filename: \"AGENTS.md\",\n strategy: \"markers\",\n },\n cursor: {\n render: renderCursorMdc,\n filename: path.join(\".cursor\", \"rules\", \"ez-context.mdc\"),\n strategy: \"direct\",\n },\n copilot: {\n render: renderCopilotMd,\n filename: path.join(\".github\", \"copilot-instructions.md\"),\n strategy: \"markers\",\n },\n skills: {\n render: renderSkillMd,\n filename: path.join(\".skills\", \"ez-context\", \"SKILL.md\"),\n strategy: \"direct\",\n },\n rulesync: {\n render: renderRulesyncMd,\n filename: path.join(\".rulesync\", \"rules\", \"ez-context.md\"),\n strategy: \"markers\",\n },\n ruler: {\n render: renderRulerMd,\n filename: path.join(\".ruler\", \"ez-context.md\"),\n strategy: \"direct\",\n },\n};\n\n// ---------------------------------------------------------------------------\n// emit()\n// ---------------------------------------------------------------------------\n\n/**\n * Emit output files for the given ConventionRegistry.\n *\n * Defaults to [\"claude\", \"agents\"] for backward compatibility.\n * In dryRun mode, returns rendered content without writing any files.\n */\nexport async function emit(\n registry: ConventionRegistry,\n options: EmitOptions\n): Promise<EmitResult> {\n const threshold = options.confidenceThreshold ?? 0.7;\n const formats: OutputFormat[] = options.formats ?? [\"claude\", \"agents\"];\n\n // Render all requested formats\n const rendered: Record<string, string> = {};\n for (const format of formats) {\n const entry = FORMAT_EMITTER_MAP[format];\n rendered[format] = entry.render(registry, threshold);\n }\n\n // Backward compat aliases\n const claudeMd = rendered[\"claude\"] ?? \"\";\n const agentsMd = rendered[\"agents\"] ?? \"\";\n\n if (options.dryRun) {\n return { rendered, claudeMd, agentsMd, filesWritten: [] };\n }\n\n // Write files\n const filesWritten: string[] = [];\n for (const format of formats) {\n const entry = FORMAT_EMITTER_MAP[format];\n const filePath = path.join(options.outputDir, entry.filename);\n\n await mkdir(path.dirname(filePath), { recursive: true });\n if (entry.strategy === \"direct\") {\n await writeFile(filePath, rendered[format]!, \"utf-8\");\n } else {\n await writeWithMarkers(filePath, rendered[format]!);\n }\n\n filesWritten.push(filePath);\n }\n\n return { rendered, claudeMd, agentsMd, filesWritten };\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -225,6 +225,64 @@ interface EmitResult {
225
225
  agentsMd: string;
226
226
  }
227
227
  //#endregion
228
+ //#region src/emitters/claude-md.d.ts
229
+ declare function renderClaudeMd(registry: ConventionRegistry, confidenceThreshold: number): string;
230
+ //#endregion
231
+ //#region src/emitters/agents-md.d.ts
232
+ declare function renderAgentsMd(registry: ConventionRegistry, confidenceThreshold: number): string;
233
+ //#endregion
234
+ //#region src/emitters/cursor-mdc.d.ts
235
+ /**
236
+ * Render a Cursor MDC rule file (.cursor/rules/ez-context.mdc).
237
+ *
238
+ * Format: YAML frontmatter + markdown body.
239
+ * - description: shown in Cursor UI
240
+ * - globs: empty string (not null/omitted) per Cursor docs
241
+ * - alwaysApply: true ensures conventions are always in context
242
+ */
243
+ declare function renderCursorMdc(registry: ConventionRegistry, confidenceThreshold: number): string;
244
+ //#endregion
245
+ //#region src/emitters/copilot-md.d.ts
246
+ /**
247
+ * Render a GitHub Copilot instructions file (.github/copilot-instructions.md).
248
+ *
249
+ * Format: Plain markdown, no YAML frontmatter.
250
+ * GitHub Copilot reads the entire file; HTML comment markers are used
251
+ * for idempotent updates via writeWithMarkers.
252
+ */
253
+ declare function renderCopilotMd(registry: ConventionRegistry, confidenceThreshold: number): string;
254
+ //#endregion
255
+ //#region src/emitters/skill-md.d.ts
256
+ /**
257
+ * Render a SKILL.md module file (.skills/ez-context/SKILL.md).
258
+ *
259
+ * Format: YAML frontmatter + markdown body.
260
+ * - name: must match directory name (ez-context)
261
+ * - description: max 1024 chars, describes what AND when to use
262
+ * Body stays under 5000 tokens (~3750 words) as per SKILL.md spec.
263
+ */
264
+ declare function renderSkillMd(registry: ConventionRegistry, confidenceThreshold: number): string;
265
+ //#endregion
266
+ //#region src/emitters/rulesync-md.d.ts
267
+ /**
268
+ * Render a Rulesync rule file (.rulesync/rules/ez-context.md).
269
+ *
270
+ * Format: YAML frontmatter + markdown body.
271
+ * - targets: specifies which AI tools receive this rule
272
+ * ez-context writes INTO .rulesync/rules/; Rulesync distributes to tools.
273
+ */
274
+ declare function renderRulesyncMd(registry: ConventionRegistry, confidenceThreshold: number): string;
275
+ //#endregion
276
+ //#region src/emitters/ruler-md.d.ts
277
+ /**
278
+ * Render a Ruler rule file (.ruler/ez-context.md).
279
+ *
280
+ * Format: Plain markdown, no YAML frontmatter.
281
+ * Ruler recursively discovers all .md files in .ruler/ and distributes them.
282
+ * ez-context writes a single .ruler/ez-context.md as an additive conventions file.
283
+ */
284
+ declare function renderRulerMd(registry: ConventionRegistry, confidenceThreshold: number): string;
285
+ //#endregion
228
286
  //#region src/emitters/index.d.ts
229
287
  /**
230
288
  * Emit output files for the given ConventionRegistry.
@@ -248,5 +306,5 @@ declare const MARKER_END = "<!-- ez-context:end -->";
248
306
  */
249
307
  declare function writeWithMarkers(filePath: string, content: string): Promise<void>;
250
308
  //#endregion
251
- export { ALWAYS_SKIP, ArchitectureInfo, ArchitectureInfoSchema, ConventionCategory, ConventionCategorySchema, ConventionEntry, ConventionEntrySchema, ConventionRegistry, ConventionRegistrySchema, type EmitOptions, type EmitResult, EvidenceRef, EvidenceRefSchema, type ExtractionContext, type Extractor, type ExtractorOptions, type EzSearchBridge, type ListFilesOptions, MARKER_END, MARKER_START, type SearchResult, StackInfo, StackInfoSchema, addConvention, createBridge, createRegistry, emit, extractConventions, listProjectFiles, runExtractors, writeWithMarkers };
309
+ export { ALWAYS_SKIP, ArchitectureInfo, ArchitectureInfoSchema, ConventionCategory, ConventionCategorySchema, ConventionEntry, ConventionEntrySchema, ConventionRegistry, ConventionRegistrySchema, type EmitOptions, type EmitResult, EvidenceRef, EvidenceRefSchema, type ExtractionContext, type Extractor, type ExtractorOptions, type EzSearchBridge, type ListFilesOptions, MARKER_END, MARKER_START, type OutputFormat, type SearchResult, StackInfo, StackInfoSchema, addConvention, createBridge, createRegistry, emit, extractConventions, listProjectFiles, renderAgentsMd, renderClaudeMd, renderCopilotMd, renderCursorMdc, renderRulerMd, renderRulesyncMd, renderSkillMd, runExtractors, writeWithMarkers };
252
310
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/core/schema.ts","../src/core/registry.ts","../src/extractors/types.ts","../src/core/pipeline.ts","../src/core/ez-search-bridge.ts","../src/utils/fs.ts","../src/extractors/index.ts","../src/emitters/types.ts","../src/emitters/index.ts","../src/emitters/writer.ts"],"mappings":";;;cAMa,wBAAA,EAAwB,CAAA,CAAA,OAAA;;;;;;;;;cAUxB,iBAAA,EAAiB,CAAA,CAAA,SAAA;;;;cAKjB,qBAAA,EAAqB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;cASrB,eAAA,EAAe,CAAA,CAAA,SAAA;;;;;;;;cASf,sBAAA,EAAsB,CAAA,CAAA,SAAA;;;;;cAUtB,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAczB,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,KACpC,WAAA,GAAc,CAAA,CAAE,KAAA,QAAa,iBAAA;AAAA,KAC7B,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,SAAA,GAAY,CAAA,CAAE,KAAA,QAAa,eAAA;AAAA,KAC3B,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,sBAAA;AAAA,KAClC,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,wBAAA;;;;;AA9DhD;;iBCIgB,cAAA,CAAe,WAAA,WAAsB,kBAAA;;;;;iBA4BrC,aAAA,CACd,QAAA,EAAU,kBAAA,EACV,KAAA,EAAO,IAAA,CAAK,eAAA,UACX,kBAAA;;;UCnCc,gBAAA;;EAEf,cAAA;AAAA;AAAA,UAOe,iBAAA;EFToB;EEWnC,WAAA;EACA,OAAA,GAAU,gBAAA;AAAA;;;;;;UAYK,SAAA;;EAEf,IAAA;EACA,OAAA,CAAQ,GAAA,EAAK,iBAAA,GAAoB,OAAA,CAAQ,IAAA,CAAK,eAAA;AAAA;;;;AF3BhD;;;;;;;;iBGmLsB,kBAAA,CACpB,WAAA,UACA,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,kBAAA;;;UC9KM,YAAA;EACf,IAAA;EACA,KAAA;EACA,KAAA;AAAA;AAAA,UAOe,cAAA;EJlBoB;;;EIsBnC,QAAA,CAAS,WAAA,WAAsB,OAAA;;;;EAK/B,WAAA,CAAY,WAAA,WAAsB,OAAA;;;;EAKlC,YAAA,CAAa,WAAA,WAAsB,OAAA;EJtBxB;;;EI2BX,MAAA,CAAO,KAAA,UAAe,OAAA;IAAY,CAAA;EAAA,IAAe,OAAA,CAAQ,YAAA;;;;;;EAOzD,KAAA,CAAM,IAAA,WAAe,OAAA;AAAA;;;;iBA+ED,YAAA,CACpB,WAAA,WACC,OAAA,CAAQ,cAAA;;;;cC5HE,WAAA;AAAA,UAaI,gBAAA;ELdJ;EKgBX,GAAA;;EAEA,UAAA;ELlBmC;EKoBnC,gBAAA;AAAA;;;;;;;iBAaoB,gBAAA,CACpB,OAAA,EAAS,gBAAA,GACR,OAAA;;;;ALnCH;;;;;;iBMKsB,aAAA,CACpB,UAAA,EAAY,SAAA,IACZ,GAAA,EAAK,iBAAA,EACL,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,kBAAA;;;KCbC,YAAA;AAAA,UASK,WAAA;;EAEf,SAAA;EPCA;EOCA,mBAAA;EPTmC;EOWnC,MAAA;EPXmC;EOanC,OAAA,GAAU,YAAA;AAAA;AAAA,UAGK,UAAA;;EAEf,QAAA,EAAU,MAAA;;EAEV,YAAA;;EAEA,QAAA;EPZW;EOcX,QAAA;AAAA;;;;APdF;;;;;iBQkEsB,IAAA,CACpB,QAAA,EAAU,kBAAA,EACV,OAAA,EAAS,WAAA,GACR,OAAA,CAAQ,UAAA;;;cClFE,YAAA;AAAA,cACA,UAAA;;ATEb;;;;;;;;iBSSsB,gBAAA,CACpB,QAAA,UACA,OAAA,WACC,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/core/schema.ts","../src/core/registry.ts","../src/extractors/types.ts","../src/core/pipeline.ts","../src/core/ez-search-bridge.ts","../src/utils/fs.ts","../src/extractors/index.ts","../src/emitters/types.ts","../src/emitters/claude-md.ts","../src/emitters/agents-md.ts","../src/emitters/cursor-mdc.ts","../src/emitters/copilot-md.ts","../src/emitters/skill-md.ts","../src/emitters/rulesync-md.ts","../src/emitters/ruler-md.ts","../src/emitters/index.ts","../src/emitters/writer.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { _ as EvidenceRefSchema, a as writeWithMarkers, c as ALWAYS_SKIP, d as addConvention, f as createRegistry, g as ConventionRegistrySchema, h as ConventionEntrySchema, i as MARKER_START, l as listProjectFiles, m as ConventionCategorySchema, n as emit, o as extractConventions, p as ArchitectureInfoSchema, r as MARKER_END, s as createBridge, u as runExtractors, v as StackInfoSchema } from "./emitters-DOtul0uF.js";
1
+ import { C as ConventionRegistrySchema, S as ConventionEntrySchema, T as StackInfoSchema, _ as runExtractors, a as renderSkillMd, b as ArchitectureInfoSchema, c as renderAgentsMd, d as MARKER_START, f as writeWithMarkers, g as listProjectFiles, h as ALWAYS_SKIP, i as renderRulesyncMd, l as renderClaudeMd, m as createBridge, n as emit, o as renderCopilotMd, p as extractConventions, r as renderRulerMd, s as renderCursorMdc, u as MARKER_END, v as addConvention, w as EvidenceRefSchema, x as ConventionCategorySchema, y as createRegistry } from "./emitters-Csij2cRu.js";
2
2
 
3
- export { ALWAYS_SKIP, ArchitectureInfoSchema, ConventionCategorySchema, ConventionEntrySchema, ConventionRegistrySchema, EvidenceRefSchema, MARKER_END, MARKER_START, StackInfoSchema, addConvention, createBridge, createRegistry, emit, extractConventions, listProjectFiles, runExtractors, writeWithMarkers };
3
+ export { ALWAYS_SKIP, ArchitectureInfoSchema, ConventionCategorySchema, ConventionEntrySchema, ConventionRegistrySchema, EvidenceRefSchema, MARKER_END, MARKER_START, StackInfoSchema, addConvention, createBridge, createRegistry, emit, extractConventions, listProjectFiles, renderAgentsMd, renderClaudeMd, renderCopilotMd, renderCursorMdc, renderRulerMd, renderRulesyncMd, renderSkillMd, runExtractors, writeWithMarkers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ez-corp/ez-context",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Extract coding conventions and generate AI context files (CLAUDE.md, AGENTS.md, Cursor rules, Copilot instructions) with semantic drift detection",
5
5
  "type": "module",
6
6
  "license": "ISC",
@@ -16,12 +16,12 @@
16
16
  ],
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "https://github.com/ez-corp/ez-context.git"
19
+ "url": "https://github.com/ezcorp-org/ez-context.git"
20
20
  },
21
21
  "bugs": {
22
- "url": "https://github.com/ez-corp/ez-context/issues"
22
+ "url": "https://github.com/ezcorp-org/ez-context/issues"
23
23
  },
24
- "homepage": "https://github.com/ez-corp/ez-context#readme",
24
+ "homepage": "https://github.com/ezcorp-org/ez-context#readme",
25
25
  "publishConfig": {
26
26
  "access": "public"
27
27
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"emitters-DOtul0uF.js","names":["EVIDENCE","parseToml","yamlLoad","prepData"],"sources":["../src/core/schema.ts","../src/core/registry.ts","../src/extractors/index.ts","../src/extractors/static/package-json.ts","../src/extractors/static/lockfile.ts","../src/extractors/static/tsconfig.ts","../src/extractors/static/go-mod.ts","../src/extractors/static/cargo-toml.ts","../src/extractors/static/ci.ts","../src/utils/fs.ts","../src/extractors/static/project-structure.ts","../src/extractors/code/naming.ts","../src/extractors/code/imports.ts","../src/extractors/code/error-handling.ts","../src/core/ez-search-bridge.ts","../src/extractors/semantic/error-handling.ts","../src/extractors/semantic/architecture.ts","../src/core/pipeline.ts","../src/emitters/writer.ts","../src/emitters/render-helpers.ts","../src/emitters/claude-md.ts","../src/emitters/agents-md.ts","../src/emitters/cursor-mdc.ts","../src/emitters/copilot-md.ts","../src/emitters/skill-md.ts","../src/emitters/rulesync-md.ts","../src/emitters/ruler-md.ts","../src/emitters/index.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Atomic schemas\n// ---------------------------------------------------------------------------\n\nexport const ConventionCategorySchema = z.enum([\n \"stack\",\n \"naming\",\n \"architecture\",\n \"error_handling\",\n \"testing\",\n \"imports\",\n \"other\",\n]);\n\nexport const EvidenceRefSchema = z.object({\n file: z.string(),\n line: z.number().int().positive().nullable(),\n});\n\nexport const ConventionEntrySchema = z.object({\n id: z.uuid(),\n category: ConventionCategorySchema,\n pattern: z.string().min(1),\n confidence: z.number().min(0).max(1),\n evidence: z.array(EvidenceRefSchema),\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport const StackInfoSchema = z.object({\n language: z.string(),\n framework: z.string().optional(),\n testRunner: z.string().optional(),\n buildTool: z.string().optional(),\n packageManager: z.string().optional(),\n nodeVersion: z.string().optional(),\n});\n\nexport const ArchitectureInfoSchema = z.object({\n pattern: z.string().optional(),\n layers: z.array(z.string()),\n entryPoints: z.array(z.string()).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Root registry schema\n// ---------------------------------------------------------------------------\n\nexport const ConventionRegistrySchema = z.object({\n version: z.literal(\"1\"),\n projectPath: z.string(),\n generatedAt: z.string().datetime(),\n stack: StackInfoSchema,\n conventions: z.array(ConventionEntrySchema),\n architecture: ArchitectureInfoSchema,\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Inferred types (no manual interfaces)\n// ---------------------------------------------------------------------------\n\nexport type ConventionCategory = z.infer<typeof ConventionCategorySchema>;\nexport type EvidenceRef = z.infer<typeof EvidenceRefSchema>;\nexport type ConventionEntry = z.infer<typeof ConventionEntrySchema>;\nexport type StackInfo = z.infer<typeof StackInfoSchema>;\nexport type ArchitectureInfo = z.infer<typeof ArchitectureInfoSchema>;\nexport type ConventionRegistry = z.infer<typeof ConventionRegistrySchema>;\n","import {\n type ConventionEntry,\n type ConventionRegistry,\n ConventionRegistrySchema,\n} from \"./schema.js\";\n\n/**\n * Create a new empty ConventionRegistry for the given project path.\n * The returned registry passes ConventionRegistrySchema validation.\n */\nexport function createRegistry(projectPath: string): ConventionRegistry {\n const registry: ConventionRegistry = {\n version: \"1\",\n projectPath,\n generatedAt: new Date().toISOString(),\n stack: {\n language: \"unknown\",\n },\n conventions: [],\n architecture: {\n layers: [],\n },\n };\n\n const result = ConventionRegistrySchema.safeParse(registry);\n if (!result.success) {\n throw new Error(\n `createRegistry produced invalid registry: ${JSON.stringify(result.error.issues)}`\n );\n }\n\n return result.data;\n}\n\n/**\n * Add a convention entry to the registry, auto-generating a UUID for the id.\n * Returns a new registry (does not mutate the input).\n */\nexport function addConvention(\n registry: ConventionRegistry,\n entry: Omit<ConventionEntry, \"id\">\n): ConventionRegistry {\n const newEntry: ConventionEntry = {\n id: crypto.randomUUID(),\n ...entry,\n };\n\n const updated: ConventionRegistry = {\n ...registry,\n conventions: [...registry.conventions, newEntry],\n };\n\n const result = ConventionRegistrySchema.safeParse(updated);\n if (!result.success) {\n throw new Error(\n `addConvention produced invalid registry: ${JSON.stringify(result.error.issues)}`\n );\n }\n\n return result.data;\n}\n","import { addConvention } from \"../core/registry.js\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"./types.js\";\n\n/**\n * Run all extractors in parallel via Promise.allSettled so that a single\n * failing extractor does not abort the others.\n *\n * Fulfilled entries are added to the registry immutably via `addConvention`.\n * Rejected extractors emit a console.warn and are skipped.\n */\nexport async function runExtractors(\n extractors: Extractor[],\n ctx: ExtractionContext,\n registry: ConventionRegistry\n): Promise<ConventionRegistry> {\n const results = await Promise.allSettled(\n extractors.map((e) => e.extract(ctx).then((entries) => ({ extractor: e, entries })))\n );\n\n let current = registry;\n\n for (const [i, result] of results.entries()) {\n if (result.status === \"fulfilled\") {\n for (const entry of result.value.entries) {\n current = addConvention(current, entry);\n }\n } else {\n console.warn(\n `[runExtractors] Extractor \"${extractors[i]!.name}\" failed:`,\n result.reason\n );\n }\n }\n\n return current;\n}\n","import { access, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Detection maps\n// ---------------------------------------------------------------------------\n\nconst FRAMEWORK_MAP: Record<string, string> = {\n react: \"React\",\n vue: \"Vue\",\n \"@angular/core\": \"Angular\",\n next: \"Next.js\",\n nuxt: \"Nuxt\",\n svelte: \"Svelte\",\n hono: \"Hono\",\n express: \"Express\",\n fastify: \"Fastify\",\n koa: \"Koa\",\n};\n\nconst TEST_RUNNER_MAP: Record<string, string> = {\n vitest: \"Vitest\",\n jest: \"Jest\",\n mocha: \"Mocha\",\n jasmine: \"Jasmine\",\n ava: \"Ava\",\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ntype PackageJson = {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n scripts?: Record<string, string>;\n type?: string;\n packageManager?: string;\n};\n\nfunction allDeps(pkg: PackageJson): Record<string, string> {\n return { ...pkg.dependencies, ...pkg.devDependencies };\n}\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\nconst EVIDENCE = [{ file: \"package.json\", line: null }];\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const packageJsonExtractor: Extractor = {\n name: \"package-json\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const filePath = join(ctx.projectPath, \"package.json\");\n\n try {\n await access(filePath);\n } catch {\n return [];\n }\n\n let pkg: PackageJson;\n try {\n const raw = await readFile(filePath, \"utf-8\");\n pkg = JSON.parse(raw) as PackageJson;\n } catch {\n return [];\n }\n\n const deps = allDeps(pkg);\n const entries: Entry[] = [];\n\n // --- Language detection ---\n const isTypeScript =\n \"typescript\" in deps || \"@types/node\" in deps;\n const language = isTypeScript ? \"TypeScript\" : \"JavaScript\";\n entries.push({\n category: \"stack\",\n pattern: `Language: ${language}`,\n confidence: 0.95,\n evidence: EVIDENCE,\n metadata: { language },\n });\n\n // --- Framework detection ---\n for (const [pkg_name, label] of Object.entries(FRAMEWORK_MAP)) {\n if (pkg_name in deps) {\n const version = deps[pkg_name];\n entries.push({\n category: \"stack\",\n pattern: `Framework: ${label}`,\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { framework: label, version },\n });\n break; // first match wins\n }\n }\n\n // --- Test runner detection ---\n for (const [pkg_name, label] of Object.entries(TEST_RUNNER_MAP)) {\n if (pkg_name in deps) {\n entries.push({\n category: \"testing\",\n pattern: `Test runner: ${label}`,\n confidence: 0.95,\n evidence: EVIDENCE,\n metadata: { testRunner: label },\n });\n break; // first match wins\n }\n }\n\n // --- Package manager detection (corepack \"packageManager\" field) ---\n if (typeof pkg.packageManager === \"string\") {\n const pmName = pkg.packageManager.split(\"@\")[0];\n if (pmName) {\n entries.push({\n category: \"stack\",\n pattern: `Package manager: ${pmName}`,\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { packageManager: pmName },\n });\n }\n }\n\n // --- ESM module system detection ---\n if (pkg.type === \"module\") {\n entries.push({\n category: \"imports\",\n pattern: `ES modules (package.json \"type\": \"module\")`,\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { moduleSystem: \"esm\" },\n });\n }\n\n // --- Scripts ---\n const scripts = pkg.scripts ?? {};\n for (const [scriptName, command] of Object.entries(scripts)) {\n const isTestScript =\n scriptName === \"test\" || scriptName.startsWith(\"test:\");\n const isBuildOrLint =\n scriptName === \"build\" || scriptName === \"lint\";\n\n if (isTestScript || isBuildOrLint) {\n entries.push({\n category: isTestScript ? \"testing\" : \"stack\",\n pattern: `Script \"${scriptName}\": ${command}`,\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { scriptName, command },\n });\n }\n }\n\n return entries;\n },\n};\n","import { access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Lockfile → package manager mapping (priority order)\n// ---------------------------------------------------------------------------\n\nconst LOCKFILES: Array<{ file: string; manager: string }> = [\n { file: \"bun.lock\", manager: \"bun\" },\n { file: \"bun.lockb\", manager: \"bun\" },\n { file: \"pnpm-lock.yaml\", manager: \"pnpm\" },\n { file: \"yarn.lock\", manager: \"yarn\" },\n { file: \"package-lock.json\", manager: \"npm\" },\n];\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\nexport const lockfileExtractor: Extractor = {\n name: \"lockfile\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n for (const { file, manager } of LOCKFILES) {\n try {\n await access(join(ctx.projectPath, file));\n return [\n {\n category: \"stack\",\n pattern: `Package manager: ${manager}`,\n confidence: 1.0,\n evidence: [{ file, line: null }],\n metadata: { packageManager: manager },\n },\n ];\n } catch {\n // not found, try next\n }\n }\n\n return [];\n },\n};\n","import { access, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype CompilerOptions = {\n strict?: boolean;\n target?: string;\n module?: string;\n moduleResolution?: string;\n paths?: Record<string, string[]>;\n [key: string]: unknown;\n};\n\ntype TsConfig = {\n compilerOptions?: CompilerOptions;\n [key: string]: unknown;\n};\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst EVIDENCE = [{ file: \"tsconfig.json\", line: null }];\n\n/**\n * Strip single-line // comments and trailing commas so JSON.parse accepts\n * tsconfig.json files that use non-standard JSON.\n */\nfunction stripTsConfigNonStandardJson(raw: string): string {\n return raw\n // Remove single-line comments (// ...)\n .replace(/\\/\\/[^\\n]*/g, \"\")\n // Remove trailing commas before ] or }\n .replace(/,(\\s*[}\\]])/g, \"$1\");\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const tsconfigExtractor: Extractor = {\n name: \"tsconfig\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const filePath = join(ctx.projectPath, \"tsconfig.json\");\n\n try {\n await access(filePath);\n } catch {\n return [];\n }\n\n let config: TsConfig;\n try {\n const raw = await readFile(filePath, \"utf-8\");\n config = JSON.parse(stripTsConfigNonStandardJson(raw)) as TsConfig;\n } catch {\n return [];\n }\n\n const co = config.compilerOptions ?? {};\n const entries: Entry[] = [];\n\n // --- Strict mode ---\n if (co.strict === true) {\n entries.push({\n category: \"stack\",\n pattern: \"TypeScript strict mode enabled\",\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { strict: true },\n });\n }\n\n // --- Notable compiler options ---\n const notable: Record<string, unknown> = {};\n for (const key of [\"target\", \"module\", \"moduleResolution\"] as const) {\n if (co[key] !== undefined) {\n notable[key] = co[key];\n }\n }\n if (Object.keys(notable).length > 0) {\n entries.push({\n category: \"stack\",\n pattern: \"TypeScript compiler options configured\",\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: notable,\n });\n }\n\n // --- Path aliases ---\n if (co.paths && Object.keys(co.paths).length > 0) {\n entries.push({\n category: \"imports\",\n pattern: \"Uses TypeScript path aliases\",\n confidence: 1.0,\n evidence: EVIDENCE,\n metadata: { aliases: co.paths },\n });\n }\n\n return entries;\n },\n};\n","import { access, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\nexport const goModExtractor: Extractor = {\n name: \"go-mod\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const filePath = join(ctx.projectPath, \"go.mod\");\n\n try {\n await access(filePath);\n } catch {\n return [];\n }\n\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf-8\");\n } catch {\n return [];\n }\n\n const lines = raw.split(\"\\n\");\n\n let moduleName = \"\";\n let goVersion = \"\";\n let dependencyCount = 0;\n let inRequireBlock = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (!moduleName) {\n const moduleMatch = trimmed.match(/^module\\s+(\\S+)/);\n if (moduleMatch?.[1]) {\n moduleName = moduleMatch[1];\n continue;\n }\n }\n\n if (!goVersion) {\n const goMatch = trimmed.match(/^go\\s+(\\S+)/);\n if (goMatch?.[1]) {\n goVersion = goMatch[1];\n continue;\n }\n }\n\n // Count deps in require blocks\n if (trimmed === \"require (\") {\n inRequireBlock = true;\n continue;\n }\n if (inRequireBlock) {\n if (trimmed === \")\") {\n inRequireBlock = false;\n } else if (trimmed.length > 0 && !trimmed.startsWith(\"//\")) {\n dependencyCount++;\n }\n continue;\n }\n // Single-line require\n if (trimmed.match(/^require\\s+\\S+\\s+v\\S+/)) {\n dependencyCount++;\n }\n }\n\n if (!moduleName) {\n return [];\n }\n\n return [\n {\n category: \"stack\",\n pattern: `Go project (${moduleName})`,\n confidence: 1.0,\n evidence: [{ file: \"go.mod\", line: null }],\n metadata: {\n language: \"Go\",\n moduleName,\n goVersion: goVersion || null,\n dependencyCount,\n },\n },\n ];\n },\n};\n","import { access, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parse as parseToml } from \"smol-toml\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\ntype CargoToml = {\n package?: { name?: string; version?: string };\n dependencies?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport const cargoTomlExtractor: Extractor = {\n name: \"cargo-toml\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const filePath = join(ctx.projectPath, \"Cargo.toml\");\n\n try {\n await access(filePath);\n } catch {\n return [];\n }\n\n let cargo: CargoToml;\n try {\n const raw = await readFile(filePath, \"utf-8\");\n cargo = parseToml(raw) as CargoToml;\n } catch {\n return [];\n }\n\n const packageName = cargo.package?.name;\n if (!packageName) {\n return [];\n }\n\n const dependencyCount = Object.keys(cargo.dependencies ?? {}).length;\n\n return [\n {\n category: \"stack\",\n pattern: `Rust project (${packageName})`,\n confidence: 1.0,\n evidence: [{ file: \"Cargo.toml\", line: null }],\n metadata: {\n language: \"Rust\",\n packageName,\n dependencyCount,\n },\n },\n ];\n },\n};\n","import { readFile } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport { globby } from \"globby\";\nimport { load as yamlLoad } from \"js-yaml\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ntype CommandCategory = \"stack\" | \"testing\";\n\ninterface CommandMatch {\n command: string;\n category: CommandCategory;\n ciFile: string;\n}\n\nconst BUILD_KEYWORDS = [\"build\", \"compile\", \"tsc\", \"tsdown\"];\nconst TEST_KEYWORDS = [\"test\", \"vitest\", \"jest\", \"pytest\", \"cargo test\", \"go test\"];\nconst LINT_KEYWORDS = [\"lint\", \"eslint\", \"biome\", \"clippy\", \"ruff\"];\n\nfunction categorizeCommand(cmd: string): CommandCategory | null {\n const lower = cmd.toLowerCase();\n if (TEST_KEYWORDS.some((kw) => lower.includes(kw))) return \"testing\";\n if (BUILD_KEYWORDS.some((kw) => lower.includes(kw))) return \"stack\";\n if (LINT_KEYWORDS.some((kw) => lower.includes(kw))) return \"stack\";\n return null;\n}\n\n/**\n * Extract run commands from a GitHub Actions workflow YAML.\n * Traverses jobs.*.steps[].run\n */\nfunction extractGithubActionsCommands(doc: unknown, filePath: string): { matched: CommandMatch[]; raw: string[] } {\n const matched: CommandMatch[] = [];\n const raw: string[] = [];\n\n if (!doc || typeof doc !== \"object\") return { matched, raw };\n const record = doc as Record<string, unknown>;\n const jobs = record[\"jobs\"];\n if (!jobs || typeof jobs !== \"object\") return { matched, raw };\n\n for (const job of Object.values(jobs as Record<string, unknown>)) {\n if (!job || typeof job !== \"object\") continue;\n const steps = (job as Record<string, unknown>)[\"steps\"];\n if (!Array.isArray(steps)) continue;\n for (const step of steps) {\n if (!step || typeof step !== \"object\") continue;\n const run = (step as Record<string, unknown>)[\"run\"];\n if (typeof run !== \"string\") continue;\n // A single run block may contain multiple lines\n for (const line of run.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n raw.push(trimmed);\n const category = categorizeCommand(trimmed);\n if (category !== null) {\n matched.push({ command: trimmed, category, ciFile: filePath });\n }\n }\n }\n }\n\n return { matched, raw };\n}\n\n/** Keys that are not job definitions in GitLab CI top-level. */\nconst GITLAB_RESERVED = new Set([\"stages\", \"variables\", \"include\", \"default\", \"workflow\", \"image\", \"services\", \"before_script\", \"after_script\", \"cache\", \"artifacts\"]);\n\n/**\n * Extract script commands from a GitLab CI YAML.\n * Traverses top-level job keys (skipping reserved keys), looks for script arrays.\n */\nfunction extractGitlabCiCommands(doc: unknown, filePath: string): { matched: CommandMatch[]; raw: string[] } {\n const matched: CommandMatch[] = [];\n const raw: string[] = [];\n\n if (!doc || typeof doc !== \"object\") return { matched, raw };\n\n for (const [key, job] of Object.entries(doc as Record<string, unknown>)) {\n if (GITLAB_RESERVED.has(key)) continue;\n if (!job || typeof job !== \"object\") continue;\n const script = (job as Record<string, unknown>)[\"script\"];\n const scripts = Array.isArray(script) ? script : typeof script === \"string\" ? [script] : [];\n for (const cmd of scripts) {\n if (typeof cmd !== \"string\") continue;\n const trimmed = cmd.trim();\n if (!trimmed) continue;\n raw.push(trimmed);\n const category = categorizeCommand(trimmed);\n if (category !== null) {\n matched.push({ command: trimmed, category, ciFile: filePath });\n }\n }\n }\n\n return { matched, raw };\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const ciExtractor: Extractor = {\n name: \"ci\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const ciPatterns = [\n \".github/workflows/*.yml\",\n \".github/workflows/*.yaml\",\n \".gitlab-ci.yml\",\n ];\n\n const ciFiles = await globby(ciPatterns, {\n cwd: ctx.projectPath,\n gitignore: false,\n followSymbolicLinks: false,\n absolute: false,\n });\n\n if (ciFiles.length === 0) return [];\n\n const entries: Entry[] = [];\n\n for (const relPath of ciFiles) {\n const absPath = join(ctx.projectPath, relPath);\n let raw: string;\n try {\n raw = await readFile(absPath, \"utf-8\");\n } catch {\n continue;\n }\n\n let doc: unknown;\n try {\n doc = yamlLoad(raw);\n } catch {\n // Malformed YAML: skip file, no throw\n continue;\n }\n\n const isGitlab = relPath.includes(\".gitlab-ci\");\n const { matched, raw: rawCmds } = isGitlab\n ? extractGitlabCiCommands(doc, relPath)\n : extractGithubActionsCommands(doc, relPath);\n\n // Store all raw commands in a single metadata entry per file\n if (rawCmds.length > 0 || matched.length > 0) {\n for (const { command, category } of matched) {\n entries.push({\n category,\n pattern: `CI command: ${command}`,\n confidence: 0.9,\n evidence: [{ file: relative(ctx.projectPath, absPath) || relPath, line: null }],\n metadata: { command, ciFile: relPath, rawCommands: rawCmds },\n });\n }\n }\n }\n\n return entries;\n },\n};\n","import { globby } from \"globby\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Directories and paths that are always excluded from file listings. */\nexport const ALWAYS_SKIP: readonly string[] = [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/generated/**\",\n \"**/.ez-search/**\",\n \"**/.ez-context/**\",\n \"**/.git/**\",\n];\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ListFilesOptions {\n /** The project root to search from. */\n cwd: string;\n /** File extensions to include (without dot). Defaults to ts, js, json, md. */\n extensions?: string[];\n /** Additional ignore patterns (glob syntax). */\n additionalIgnore?: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Utility\n// ---------------------------------------------------------------------------\n\n/**\n * List project files while respecting .gitignore (INTG-04) and always\n * skipping common generated/build directories.\n *\n * @returns Relative paths sorted alphabetically.\n */\nexport async function listProjectFiles(\n options: ListFilesOptions\n): Promise<string[]> {\n const {\n cwd,\n extensions = [\"ts\", \"js\", \"json\", \"md\"],\n additionalIgnore = [],\n } = options;\n\n const extPattern =\n extensions.length === 1\n ? `**/*.${extensions[0]}`\n : `**/*.{${extensions.join(\",\")}}`;\n\n const files = await globby(extPattern, {\n cwd,\n gitignore: true,\n ignore: [...ALWAYS_SKIP, ...additionalIgnore],\n followSymbolicLinks: false,\n absolute: false,\n });\n\n return files.sort();\n}\n","import { globby } from \"globby\";\nimport { ALWAYS_SKIP } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\ninterface TestPattern {\n glob: string;\n location: string;\n style: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Top-level test directory prefixes — files here are directory-based, not co-located. */\nconst TEST_DIR_PREFIXES = [\"test/\", \"tests/\", \"__tests__/\"];\n\nconst TEST_PATTERNS: TestPattern[] = [\n { glob: \"**/*.test.{ts,tsx,js,jsx}\", location: \"co-located\", style: \"*.test.ts style\" },\n { glob: \"**/*.spec.{ts,tsx,js,jsx}\", location: \"co-located\", style: \"*.spec.ts style\" },\n { glob: \"test/**/*.{ts,tsx,js,jsx}\", location: \"test/ directory\", style: \"test/ directory\" },\n { glob: \"tests/**/*.{ts,tsx,js,jsx}\", location: \"tests/ directory\", style: \"tests/ directory\" },\n { glob: \"__tests__/**/*.{ts,tsx,js,jsx}\", location: \"__tests__/ directory\", style: \"__tests__/ directory\" },\n];\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const projectStructureExtractor: Extractor = {\n name: \"project-structure\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const entries: Entry[] = [];\n\n for (const { glob, location, style } of TEST_PATTERNS) {\n let matches = await globby(glob, {\n cwd: ctx.projectPath,\n gitignore: true,\n ignore: [...ALWAYS_SKIP],\n followSymbolicLinks: false,\n absolute: false,\n });\n\n // For co-located patterns, exclude files that live in dedicated test directories\n if (location === \"co-located\") {\n matches = matches.filter(\n (f) => !TEST_DIR_PREFIXES.some((prefix) => f.startsWith(prefix))\n );\n }\n\n if (matches.length === 0) continue;\n\n const count = matches.length;\n const confidence = Math.min(0.95, 0.5 + count * 0.05);\n const evidenceFiles = matches.slice(0, 5);\n\n entries.push({\n category: \"testing\",\n pattern: `Test files in ${location} (${style})`,\n confidence,\n evidence: evidenceFiles.map((f) => ({ file: f, line: null })),\n metadata: {\n testFileCount: count,\n location,\n style,\n },\n });\n }\n\n return entries;\n },\n};\n","import { Project } from \"ts-morph\";\nimport { listProjectFiles } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\ntype CaseKind = \"camelCase\" | \"PascalCase\" | \"snake_case\" | \"UPPER_SNAKE_CASE\";\n\n// ---------------------------------------------------------------------------\n// Case classification\n// ---------------------------------------------------------------------------\n\n/**\n * Classify the naming convention of a single identifier.\n * Returns null for short names (< 4 chars) or unclassifiable names.\n */\nfunction classifyCase(name: string): CaseKind | null {\n if (name.length < 4) return null;\n\n if (/^[A-Z][A-Z0-9_]{3,}$/.test(name)) return \"UPPER_SNAKE_CASE\";\n if (/^[A-Z][a-zA-Z0-9]+$/.test(name)) return \"PascalCase\";\n if (/^[a-z][a-z0-9_]+$/.test(name) && name.includes(\"_\")) return \"snake_case\";\n if (/^[a-z][a-zA-Z0-9]+$/.test(name) && /[A-Z]/.test(name)) return \"camelCase\";\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const namingExtractor: Extractor = {\n name: \"naming\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const files = await listProjectFiles({\n cwd: ctx.projectPath,\n extensions: [\"ts\", \"tsx\", \"js\", \"jsx\"],\n });\n\n if (files.length === 0) return [];\n\n const maxFiles = ctx.options?.maxFilesForAst ?? 200;\n const filesToAnalyse = files.slice(0, maxFiles);\n\n const project = new Project({\n compilerOptions: { allowJs: true, noEmit: true },\n skipFileDependencyResolution: true,\n });\n\n // Absolute paths needed for ts-morph\n const absPaths = filesToAnalyse.map((f) => `${ctx.projectPath}/${f}`);\n project.addSourceFilesAtPaths(absPaths);\n\n // Track counts: { functions, variables, classes } -> { CaseKind -> count }\n const functions: Record<string, number> = {};\n const variables: Record<string, number> = {};\n const classes: Record<string, number> = {};\n const counts: Record<string, Record<string, number>> = { functions, variables, classes };\n\n for (const sf of project.getSourceFiles()) {\n for (const fn of sf.getFunctions()) {\n const name = fn.getName();\n if (!name) continue;\n const kind = classifyCase(name);\n if (kind) functions[kind] = (functions[kind] ?? 0) + 1;\n }\n\n for (const decl of sf.getVariableDeclarations()) {\n const name = decl.getName();\n const kind = classifyCase(name);\n if (kind) variables[kind] = (variables[kind] ?? 0) + 1;\n }\n\n for (const cls of sf.getClasses()) {\n const name = cls.getName();\n if (!name) continue;\n const kind = classifyCase(name);\n if (kind) classes[kind] = (classes[kind] ?? 0) + 1;\n }\n }\n\n const entries: Entry[] = [];\n\n for (const [entityType, caseCounts] of Object.entries(counts)) {\n const total = Object.values(caseCounts).reduce((a, b) => a + b, 0);\n if (total < 3) continue;\n\n // Find dominant case\n let dominant: CaseKind | null = null;\n let dominantCount = 0;\n for (const [kind, count] of Object.entries(caseCounts)) {\n if (count > dominantCount) {\n dominant = kind as CaseKind;\n dominantCount = count;\n }\n }\n\n if (!dominant) continue;\n\n const confidence = Math.min(0.95, dominantCount / total);\n if (confidence < 0.6) continue;\n\n entries.push({\n category: \"naming\",\n pattern: `${entityType} use ${dominant} naming`,\n confidence,\n evidence: [{ file: \"src/**/*.ts\", line: null }],\n metadata: {\n entityType,\n dominantCase: dominant,\n counts: caseCounts,\n sampleSize: total,\n },\n });\n }\n\n return entries;\n },\n};\n","import { join, dirname, resolve } from \"node:path\";\nimport { Project } from \"ts-morph\";\nimport { listProjectFiles } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\ntype SourceFile = ReturnType<InstanceType<typeof Project>[\"getSourceFiles\"]>[number];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Known path alias prefixes used across JS/TS ecosystems. */\nconst ALIAS_PREFIXES = [\"@/\", \"~/\", \"#/\", \"$lib/\"];\n\nfunction hasPathAlias(specifier: string): boolean {\n return ALIAS_PREFIXES.some((prefix) => specifier.startsWith(prefix));\n}\n\n/**\n * Check whether a source file looks like a barrel file:\n * - Has at least one export declaration\n * - Has no function, class, or variable declarations\n */\nfunction isBarrelFile(sourceFile: SourceFile): boolean {\n const hasExports = sourceFile.getExportDeclarations().length > 0;\n if (!hasExports) return false;\n const hasFunctions = sourceFile.getFunctions().length > 0;\n const hasClasses = sourceFile.getClasses().length > 0;\n const hasVars = sourceFile.getVariableDeclarations().length > 0;\n return !hasFunctions && !hasClasses && !hasVars;\n}\n\n/**\n * Build a set of absolute paths for barrel files (index.ts/js variants).\n * Since skipFileDependencyResolution prevents module resolution,\n * we identify barrel files upfront and match imports by path.\n */\nfunction buildBarrelFileSet(project: Project): Set<string> {\n const barrels = new Set<string>();\n for (const sf of project.getSourceFiles()) {\n const filePath = sf.getFilePath();\n const baseName = filePath.split(\"/\").pop() ?? \"\";\n // Only index files can be barrel files (index.ts, index.tsx, index.js, index.jsx)\n if (/^index\\.[tj]sx?$/.test(baseName) && isBarrelFile(sf)) {\n barrels.add(filePath);\n }\n }\n return barrels;\n}\n\n/** Resolve a relative import specifier to candidate absolute paths. */\nfunction resolveRelativeImport(importingFile: string, specifier: string): string[] {\n const dir = dirname(importingFile);\n const base = resolve(dir, specifier);\n const exts = [\".ts\", \".tsx\", \".js\", \".jsx\"];\n const candidates: string[] = [];\n // Direct file: ./foo -> ./foo.ts etc\n for (const ext of exts) candidates.push(base + ext);\n // Directory index: ./foo -> ./foo/index.ts etc\n for (const ext of exts) candidates.push(join(base, \"index\" + ext));\n return candidates;\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const importsExtractor: Extractor = {\n name: \"imports\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const files = await listProjectFiles({\n cwd: ctx.projectPath,\n extensions: [\"ts\", \"tsx\", \"js\", \"jsx\"],\n });\n\n if (files.length === 0) return [];\n\n const maxFiles = ctx.options?.maxFilesForAst ?? 200;\n const filesToAnalyse = files.slice(0, maxFiles);\n\n const project = new Project({\n compilerOptions: { allowJs: true, noEmit: true },\n skipFileDependencyResolution: true,\n });\n\n const absPaths = filesToAnalyse.map((f) => `${ctx.projectPath}/${f}`);\n project.addSourceFilesAtPaths(absPaths);\n\n // Pre-compute barrel file set for path-based matching\n const barrelFiles = buildBarrelFileSet(project);\n\n let relativeCount = 0;\n let externalCount = 0;\n let barrelCount = 0;\n let aliasCount = 0;\n\n for (const sf of project.getSourceFiles()) {\n for (const imp of sf.getImportDeclarations()) {\n const specifier = imp.getModuleSpecifierValue();\n\n if (imp.isModuleSpecifierRelative()) {\n relativeCount++;\n\n // Barrel detection via path heuristic\n const candidates = resolveRelativeImport(sf.getFilePath(), specifier);\n if (candidates.some((c) => barrelFiles.has(c))) {\n barrelCount++;\n }\n } else {\n externalCount++;\n\n if (hasPathAlias(specifier)) {\n aliasCount++;\n }\n }\n }\n }\n\n const totalImports = relativeCount + externalCount;\n if (totalImports === 0) return [];\n\n const entries: Entry[] = [];\n const evidence = [{ file: \"src/**/*.ts\", line: null }];\n\n // --- Import organization pattern ---\n const relRatio = relativeCount / totalImports;\n let orgPattern: string;\n if (relRatio >= 0.75) {\n orgPattern = \"Predominantly relative imports\";\n } else if (relRatio <= 0.25) {\n orgPattern = \"Predominantly external imports\";\n } else {\n orgPattern = \"Mix of relative and external imports\";\n }\n\n // Confidence scales with sample size (saturates at 100 imports)\n const sizeConfidence = Math.min(0.95, 0.5 + (totalImports / 100) * 0.45);\n\n entries.push({\n category: \"imports\",\n pattern: orgPattern,\n confidence: sizeConfidence,\n evidence,\n metadata: {\n relativeCount,\n externalCount,\n totalImports,\n relativeRatio: Math.round(relRatio * 100) / 100,\n },\n });\n\n // --- Barrel file usage ---\n if (barrelCount > 0) {\n const barrelRatio = barrelCount / relativeCount;\n entries.push({\n category: \"imports\",\n pattern: \"Uses barrel file (index) imports\",\n confidence: Math.min(0.95, 0.5 + barrelRatio * 0.45),\n evidence,\n metadata: { barrelCount, relativeCount },\n });\n }\n\n // --- Path alias usage ---\n if (aliasCount > 0) {\n entries.push({\n category: \"imports\",\n pattern: \"Uses path aliases (@/ prefix)\",\n confidence: 1.0,\n evidence,\n metadata: { aliasCount },\n });\n }\n\n return entries;\n },\n};\n","import { Project, SyntaxKind } from \"ts-morph\";\nimport { listProjectFiles } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const staticErrorHandlingExtractor: Extractor = {\n name: \"static-error-handling\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const files = await listProjectFiles({\n cwd: ctx.projectPath,\n extensions: [\"ts\", \"tsx\", \"js\", \"jsx\"],\n });\n\n if (files.length === 0) return [];\n\n const maxFiles = ctx.options?.maxFilesForAst ?? 200;\n const filesToAnalyse = files.slice(0, maxFiles);\n\n const project = new Project({\n compilerOptions: { allowJs: true, noEmit: true },\n skipFileDependencyResolution: true,\n });\n\n const absPaths = filesToAnalyse.map((f) => `${ctx.projectPath}/${f}`);\n project.addSourceFilesAtPaths(absPaths);\n\n let tryCatchFileCount = 0;\n let tryCatchTotalCount = 0;\n let customErrorClassCount = 0;\n const tryCatchEvidence: string[] = [];\n const customErrorEvidence: string[] = [];\n\n for (const sf of project.getSourceFiles()) {\n const relPath = sf.getFilePath().replace(ctx.projectPath + \"/\", \"\");\n\n const tryStatements = sf.getDescendantsOfKind(SyntaxKind.TryStatement);\n if (tryStatements.length > 0) {\n tryCatchFileCount++;\n tryCatchTotalCount += tryStatements.length;\n if (tryCatchEvidence.length < 5) tryCatchEvidence.push(relPath);\n }\n\n for (const cls of sf.getClasses()) {\n const heritage = cls.getExtends();\n if (heritage && /\\bError\\b/.test(heritage.getText())) {\n customErrorClassCount++;\n if (customErrorEvidence.length < 5) customErrorEvidence.push(relPath);\n }\n }\n }\n\n const entries: Entry[] = [];\n const totalFiles = filesToAnalyse.length;\n\n // Require at least 2 try/catch occurrences (across 1+ files) for a meaningful pattern\n if (tryCatchTotalCount >= 2) {\n // Confidence based on both file spread and occurrence density\n const fileSpread = tryCatchFileCount / totalFiles;\n const densityBoost = Math.min(0.2, tryCatchTotalCount * 0.05);\n const confidence = Math.min(0.95, 0.5 + fileSpread * 0.35 + densityBoost);\n entries.push({\n category: \"error_handling\",\n pattern: \"try/catch imperative error handling\",\n confidence,\n evidence: tryCatchEvidence.map((file) => ({ file, line: null })),\n metadata: { style: \"try-catch\", fileCount: tryCatchFileCount, totalCount: tryCatchTotalCount },\n });\n }\n\n if (customErrorClassCount >= 1) {\n const confidence = Math.min(0.95, 0.5 + (customErrorClassCount / totalFiles) * 0.45);\n entries.push({\n category: \"error_handling\",\n pattern: \"custom error class hierarchy\",\n confidence,\n evidence: customErrorEvidence.map((file) => ({ file, line: null })),\n metadata: { style: \"custom-error-class\", classCount: customErrorClassCount },\n });\n }\n\n return entries;\n },\n};\n","/**\n * ez-search bridge — thin adapter over @ez-corp/ez-search.\n *\n * This is the ONLY file that imports from @ez-corp/ez-search.\n * All other modules interact with ez-search via the EzSearchBridge interface.\n */\nimport { index, query, EzSearchError } from \"@ez-corp/ez-search\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Supporting types\n// ---------------------------------------------------------------------------\n\nexport interface SearchResult {\n file: string;\n chunk: string;\n score: number;\n}\n\n// ---------------------------------------------------------------------------\n// Bridge interface\n// ---------------------------------------------------------------------------\n\nexport interface EzSearchBridge {\n /**\n * Returns true if an .ez-search/ index exists for the project directory.\n */\n hasIndex(projectPath: string): Promise<boolean>;\n\n /**\n * Ensures an index exists: indexes the project if no index is present (EXTR-10).\n */\n ensureIndex(projectPath: string): Promise<void>;\n\n /**\n * Unconditionally re-indexes the project so search results reflect current file state.\n */\n refreshIndex(projectPath: string): Promise<void>;\n\n /**\n * Semantic (and hybrid) search over the indexed project.\n */\n search(query: string, options?: { k?: number }): Promise<SearchResult[]>;\n\n /**\n * Get an embedding vector for the given text.\n * NOTE: @ez-corp/ez-search does not expose a standalone embed API.\n * This method is reserved for future use or a companion embedder.\n */\n embed(text: string): Promise<number[]>;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nclass EzSearchBridgeImpl implements EzSearchBridge {\n constructor(private readonly projectPath: string) {}\n\n async hasIndex(projectPath: string): Promise<boolean> {\n const indexDir = join(projectPath, \".ez-search\");\n return existsSync(indexDir);\n }\n\n async ensureIndex(projectPath: string): Promise<void> {\n if (await this.hasIndex(projectPath)) {\n return;\n }\n await index(projectPath);\n }\n\n async refreshIndex(projectPath: string): Promise<void> {\n // Incremental: ez-search >=1.3.3 skips unchanged files (mtime+size+hash),\n // only re-embeds modified chunks, and auto-recovers from stale Zvec locks.\n await index(projectPath);\n }\n\n async search(\n searchQuery: string,\n options: { k?: number } = {}\n ): Promise<SearchResult[]> {\n const { k = 10 } = options;\n\n let raw: Awaited<ReturnType<typeof query>>;\n try {\n raw = await query(searchQuery, {\n topK: k,\n projectDir: this.projectPath,\n autoIndex: false,\n });\n } catch (err: unknown) {\n if (err instanceof EzSearchError && err.code === \"NO_INDEX\") {\n return [];\n }\n throw err;\n }\n\n const results: SearchResult[] = [];\n\n for (const hit of raw.code) {\n results.push({ file: hit.file, chunk: hit.text, score: hit.score });\n }\n for (const hit of raw.text) {\n results.push({ file: hit.file, chunk: hit.text, score: hit.score });\n }\n\n results.sort((a, b) => b.score - a.score);\n return results.slice(0, k);\n }\n\n async embed(_text: string): Promise<number[]> {\n // @ez-corp/ez-search does not expose a standalone embed endpoint.\n // This is a planned capability that will be implemented when a\n // companion embedding API becomes available.\n throw new Error(\n \"embed() is not yet supported by the ez-search bridge. \" +\n \"Use search() for semantic retrieval.\"\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an EzSearchBridge bound to the given project directory.\n */\nexport async function createBridge(\n projectPath: string\n): Promise<EzSearchBridge> {\n return new EzSearchBridgeImpl(projectPath);\n}\n","import { createBridge } from \"../../core/ez-search-bridge.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\ntype ErrorStyle = \"try-catch\" | \"result-type\" | \"custom-error-class\" | \"error-boundary\";\n\ninterface PatternDef {\n style: ErrorStyle;\n pattern: string;\n test: (content: string) => boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Pattern definitions\n// ---------------------------------------------------------------------------\n\nconst PATTERNS: PatternDef[] = [\n {\n style: \"try-catch\",\n pattern: \"try/catch imperative error handling\",\n test: (content) => /\\btry\\s*\\{/.test(content) && /\\bcatch\\s*\\(/.test(content),\n },\n {\n style: \"result-type\",\n pattern: \"Result/Either functional error handling\",\n test: (content) =>\n /\\bResult<|\\bOk\\(|\\bErr\\(|\\bisOk\\b|\\bisErr\\b|\\bneverthrow\\b/.test(content),\n },\n {\n style: \"custom-error-class\",\n pattern: \"custom error class hierarchy\",\n test: (content) => /class\\s+\\w+Error\\b|\\bnew\\s+\\w+Error\\(/.test(content),\n },\n {\n style: \"error-boundary\",\n pattern: \"React error boundary components\",\n test: (content) => /\\bErrorBoundary\\b|\\bcomponentDidCatch\\b/.test(content),\n },\n];\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const errorHandlingExtractor: Extractor = {\n name: \"error-handling\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n const bridge = await createBridge(ctx.projectPath);\n\n if (!(await bridge.hasIndex(ctx.projectPath))) {\n return [];\n }\n\n // Issue targeted search queries\n const [general, resultType, customErrors] = await Promise.all([\n bridge.search(\"error handling try catch throw exception\", { k: 30 }),\n bridge.search(\"Result Ok Err return error value\", { k: 20 }),\n bridge.search(\"custom error class extends Error\", { k: 20 }),\n ]);\n\n // Merge and deduplicate chunks by file (concatenate text for same file)\n const fileContentMap = new Map<string, string>();\n for (const result of [...general, ...resultType, ...customErrors]) {\n const existing = fileContentMap.get(result.file) ?? \"\";\n fileContentMap.set(result.file, existing + \"\\n\" + result.chunk);\n }\n\n const totalUniqueFiles = fileContentMap.size;\n if (totalUniqueFiles === 0) return [];\n\n const entries: Entry[] = [];\n\n for (const patternDef of PATTERNS) {\n const matchingFiles: string[] = [];\n\n for (const [file, content] of fileContentMap) {\n if (patternDef.test(content)) {\n matchingFiles.push(file);\n }\n }\n\n // Require at least 2 distinct files\n if (matchingFiles.length < 2) continue;\n\n const confidence = Math.min(0.95, 0.5 + (matchingFiles.length / totalUniqueFiles) * 0.45);\n\n entries.push({\n category: \"error_handling\",\n pattern: patternDef.pattern,\n confidence,\n evidence: matchingFiles.slice(0, 5).map((file) => ({ file, line: null })),\n metadata: {\n style: patternDef.style,\n fileCount: matchingFiles.length,\n },\n });\n }\n\n return entries;\n },\n};\n","import { createBridge } from \"../../core/ez-search-bridge.js\";\nimport { listProjectFiles } from \"../../utils/fs.js\";\nimport type { ConventionEntry } from \"../../core/schema.js\";\nimport type { Extractor, ExtractionContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Entry = Omit<ConventionEntry, \"id\">;\n\ntype ArchPattern = \"MVC\" | \"feature-based\" | \"layer-based\";\n\n// ---------------------------------------------------------------------------\n// Directory pattern detection\n// ---------------------------------------------------------------------------\n\n/** Get directory paths under src/ (or project root if no src/). */\nfunction extractSourceDirs(files: string[]): Set<string> {\n const dirs = new Set<string>();\n for (const f of files) {\n const parts = f.split(\"/\");\n if (parts.length < 2) continue;\n\n if (parts[0] === \"src\" && parts.length >= 3) {\n // Under src/ -- use \"src/subdir\" as the dir\n dirs.add(`src/${parts[1]}`);\n } else if (parts[0] !== \"src\") {\n // Project root level\n dirs.add(parts[0]!);\n }\n }\n return dirs;\n}\n\n/** Normalise a directory name to lower case for matching. */\nfunction normalise(dir: string): string {\n return dir.split(\"/\").pop()!.toLowerCase();\n}\n\n/** Detect MVC: >= 2 of models/, views/, controllers/, routes/ */\nfunction detectMVC(sourceDirs: Set<string>): string[] {\n const mvc = [\"model\", \"models\", \"view\", \"views\", \"controller\", \"controllers\", \"route\", \"routes\"];\n const found: string[] = [];\n for (const dir of sourceDirs) {\n if (mvc.includes(normalise(dir))) found.push(dir);\n }\n // Deduplicate by canonical name (model/models both count as one)\n const canonical = new Set(found.map((d) => normalise(d).replace(/s$/, \"\")));\n return canonical.size >= 2 ? found : [];\n}\n\n/** Detect feature-based: files under features/, modules/, pages/ with >= 5 files total. */\nfunction detectFeatureBased(files: string[]): string[] {\n const featurePattern = /\\/(features?|modules?|pages?)\\//i;\n const featureDirs = new Set<string>();\n let count = 0;\n for (const f of files) {\n if (featurePattern.test(f)) {\n count++;\n // Extract the feature root dir (e.g. \"src/features\" or \"features\")\n const match = f.match(/^(.*?\\/(features?|modules?|pages?))\\//i);\n if (match) featureDirs.add(match[1]!);\n }\n }\n return count >= 5 ? Array.from(featureDirs) : [];\n}\n\n/** Detect layer-based architecture (DDD / hexagonal / clean arch). */\nfunction detectLayerBased(sourceDirs: Set<string>): string[] {\n const layerPatterns = [\n // DDD\n \"domain\", \"application\", \"infrastructure\",\n // Hexagonal\n \"service\", \"services\", \"repository\", \"repositories\", \"handler\", \"handlers\", \"usecase\", \"usecases\",\n // Clean arch\n \"core\", \"data\", \"presentation\",\n ];\n const found: string[] = [];\n for (const dir of sourceDirs) {\n if (layerPatterns.includes(normalise(dir))) found.push(dir);\n }\n // Need >= 2 distinct layer dirs\n const canonical = new Set(found.map((d) => normalise(d)));\n return canonical.size >= 2 ? found : [];\n}\n\n// ---------------------------------------------------------------------------\n// Extractor\n// ---------------------------------------------------------------------------\n\nexport const architectureExtractor: Extractor = {\n name: \"architecture\",\n\n async extract(ctx: ExtractionContext): Promise<Entry[]> {\n // Signal 1: Directory structure scan (deterministic)\n const files = await listProjectFiles({\n cwd: ctx.projectPath,\n extensions: [\"ts\", \"js\", \"tsx\", \"jsx\", \"py\", \"rb\", \"go\", \"rs\"],\n });\n\n const sourceDirs = extractSourceDirs(files);\n\n // Try each pattern\n const mvcDirs = detectMVC(sourceDirs);\n const featureDirs = detectFeatureBased(files);\n const layerDirs = detectLayerBased(sourceDirs);\n\n let detectedPattern: ArchPattern | null = null;\n let detectedLayers: string[] = [];\n\n if (mvcDirs.length > 0) {\n detectedPattern = \"MVC\";\n detectedLayers = mvcDirs;\n } else if (featureDirs.length > 0) {\n detectedPattern = \"feature-based\";\n detectedLayers = featureDirs;\n } else if (layerDirs.length > 0) {\n detectedPattern = \"layer-based\";\n detectedLayers = layerDirs;\n }\n\n if (!detectedPattern) return [];\n\n // Signal 2: Semantic search confirmation (optional -- adds evidence and boosts confidence)\n const bridge = await createBridge(ctx.projectPath);\n const hasIdx = await bridge.hasIndex(ctx.projectPath);\n\n let confidence = 0.7; // directory-only\n const evidence: { file: string; line: null }[] = detectedLayers\n .slice(0, 3)\n .map((dir) => ({ file: dir, line: null }));\n\n if (hasIdx) {\n const searchResults = await bridge.search(\n \"model view controller route handler service repository\",\n { k: 20 }\n );\n if (searchResults.length > 0) {\n confidence = 0.85;\n for (const r of searchResults.slice(0, 2)) {\n evidence.push({ file: r.file, line: null });\n }\n }\n }\n\n // Deduplicate evidence by file\n const seen = new Set<string>();\n const deduped = evidence.filter(({ file }) => {\n if (seen.has(file)) return false;\n seen.add(file);\n return true;\n });\n\n return [\n {\n category: \"architecture\",\n pattern: patternLabel(detectedPattern),\n confidence,\n evidence: deduped.slice(0, 5),\n metadata: {\n architecturePattern: detectedPattern,\n layers: detectedLayers,\n },\n },\n ];\n },\n};\n\nfunction patternLabel(p: ArchPattern): string {\n switch (p) {\n case \"MVC\":\n return \"MVC architecture pattern\";\n case \"feature-based\":\n return \"Feature-based architecture\";\n case \"layer-based\":\n return \"Layer-based architecture\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers exposed for testing\n// ---------------------------------------------------------------------------\n\nexport { extractSourceDirs, detectMVC, detectFeatureBased, detectLayerBased };\n","import { createRegistry } from \"./registry.js\";\nimport { ConventionRegistrySchema } from \"./schema.js\";\nimport type { ConventionEntry, ConventionRegistry, EvidenceRef } from \"./schema.js\";\nimport { runExtractors } from \"../extractors/index.js\";\nimport type { ExtractorOptions } from \"../extractors/types.js\";\nimport { packageJsonExtractor } from \"../extractors/static/package-json.js\";\nimport { lockfileExtractor } from \"../extractors/static/lockfile.js\";\nimport { tsconfigExtractor } from \"../extractors/static/tsconfig.js\";\nimport { goModExtractor } from \"../extractors/static/go-mod.js\";\nimport { cargoTomlExtractor } from \"../extractors/static/cargo-toml.js\";\nimport { ciExtractor } from \"../extractors/static/ci.js\";\nimport { projectStructureExtractor } from \"../extractors/static/project-structure.js\";\nimport { namingExtractor } from \"../extractors/code/naming.js\";\nimport { importsExtractor } from \"../extractors/code/imports.js\";\nimport { staticErrorHandlingExtractor } from \"../extractors/code/error-handling.js\";\nimport { errorHandlingExtractor } from \"../extractors/semantic/error-handling.js\";\nimport { architectureExtractor } from \"../extractors/semantic/architecture.js\";\n\n// ---------------------------------------------------------------------------\n// Extractor registry (ordered by confidence priority for StackInfo population)\n// ---------------------------------------------------------------------------\n\nconst ALL_EXTRACTORS = [\n packageJsonExtractor,\n lockfileExtractor,\n tsconfigExtractor,\n goModExtractor,\n cargoTomlExtractor,\n ciExtractor,\n projectStructureExtractor,\n namingExtractor,\n importsExtractor,\n staticErrorHandlingExtractor,\n errorHandlingExtractor,\n architectureExtractor,\n];\n\n// ---------------------------------------------------------------------------\n// Deduplication helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Deduplicate evidence by file+line combination.\n */\nfunction deduplicateEvidence(evidence: EvidenceRef[]): EvidenceRef[] {\n const seen = new Set<string>();\n return evidence.filter((e) => {\n const key = `${e.file}:${e.line ?? \"null\"}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/**\n * Deduplicate conventions by category+pattern.\n * For duplicates, keep the one with higher confidence.\n * Merge evidence arrays from duplicates.\n */\nfunction deduplicateConventions(\n conventions: ConventionEntry[]\n): ConventionEntry[] {\n const grouped = new Map<string, ConventionEntry>();\n\n for (const entry of conventions) {\n const key = `${entry.category}:${entry.pattern}`;\n const existing = grouped.get(key);\n\n if (!existing) {\n grouped.set(key, entry);\n } else {\n // Keep higher confidence, merge evidence\n const winner: ConventionEntry =\n entry.confidence > existing.confidence ? entry : existing;\n const mergedEvidence = deduplicateEvidence([\n ...existing.evidence,\n ...entry.evidence,\n ]);\n grouped.set(key, { ...winner, evidence: mergedEvidence });\n }\n }\n\n return Array.from(grouped.values());\n}\n\n// ---------------------------------------------------------------------------\n// StackInfo population helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Populate StackInfo from extracted conventions via a post-extraction pass.\n * Conventions are processed in array order; first match wins for each field.\n */\nfunction populateStackInfo(registry: ConventionRegistry): ConventionRegistry {\n const stack = { ...registry.stack };\n\n for (const entry of registry.conventions) {\n const meta = entry.metadata ?? {};\n\n if (entry.category === \"stack\") {\n if (!stack.language || stack.language === \"unknown\") {\n if (typeof meta.language === \"string\") {\n stack.language = meta.language;\n }\n }\n if (!stack.framework && typeof meta.framework === \"string\") {\n stack.framework = meta.framework;\n }\n if (!stack.testRunner && typeof meta.testRunner === \"string\") {\n stack.testRunner = meta.testRunner;\n }\n if (!stack.packageManager && typeof meta.packageManager === \"string\") {\n stack.packageManager = meta.packageManager;\n }\n if (!stack.buildTool) {\n // Detect buildTool from scripts metadata\n if (typeof meta.buildTool === \"string\") {\n stack.buildTool = meta.buildTool;\n } else if (\n typeof meta.scriptName === \"string\" &&\n meta.scriptName === \"build\" &&\n typeof meta.command === \"string\"\n ) {\n // Extract the build tool from the build script command (first word)\n stack.buildTool = (meta.command as string).split(\" \")[0];\n }\n }\n }\n\n if (entry.category === \"testing\") {\n if (!stack.testRunner && typeof meta.testRunner === \"string\") {\n stack.testRunner = meta.testRunner;\n }\n }\n }\n\n return { ...registry, stack };\n}\n\n// ---------------------------------------------------------------------------\n// ArchitectureInfo population helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Populate ArchitectureInfo from extracted conventions via a post-extraction pass.\n * First architecture convention with the relevant metadata wins.\n */\nfunction populateArchitectureInfo(\n registry: ConventionRegistry\n): ConventionRegistry {\n const arch = { ...registry.architecture };\n\n for (const entry of registry.conventions) {\n if (entry.category === \"architecture\") {\n if (\n !arch.pattern &&\n typeof entry.metadata?.architecturePattern === \"string\"\n ) {\n arch.pattern = entry.metadata.architecturePattern;\n }\n if (\n arch.layers.length === 0 &&\n Array.isArray(entry.metadata?.layers)\n ) {\n arch.layers = entry.metadata.layers as string[];\n }\n }\n }\n\n return { ...registry, architecture: arch };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Run the full extraction pipeline against the given project path.\n *\n * 1. Runs all extractors in parallel (Promise.allSettled)\n * 2. Deduplicates conventions by category+pattern (higher confidence wins)\n * 3. Populates StackInfo from convention metadata\n * 4. Populates ArchitectureInfo from convention metadata\n * 5. Validates and returns the final ConventionRegistry\n */\nexport async function extractConventions(\n projectPath: string,\n options?: ExtractorOptions\n): Promise<ConventionRegistry> {\n const ctx = { projectPath, options };\n const emptyRegistry = createRegistry(projectPath);\n\n // Run all extractors\n const populated = await runExtractors(ALL_EXTRACTORS, ctx, emptyRegistry);\n\n // Deduplicate conventions\n const deduplicated: ConventionRegistry = {\n ...populated,\n conventions: deduplicateConventions(populated.conventions),\n };\n\n // Populate StackInfo from convention metadata\n const withStack = populateStackInfo(deduplicated);\n\n // Populate ArchitectureInfo from convention metadata\n const withArch = populateArchitectureInfo(withStack);\n\n // Validate and return\n return ConventionRegistrySchema.parse(withArch);\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\n\nexport const MARKER_START = \"<!-- ez-context:start -->\";\nexport const MARKER_END = \"<!-- ez-context:end -->\";\n\n/**\n * Write content into the marker section of filePath.\n *\n * Three paths:\n * 1. File does not exist: creates it with markers wrapping content.\n * 2. File exists, no markers (or only one marker): appends the section at the end.\n * 3. File exists with both markers: splices new content between existing markers,\n * preserving everything outside.\n */\nexport async function writeWithMarkers(\n filePath: string,\n content: string\n): Promise<void> {\n const wrapped = `${MARKER_START}\\n${content}\\n${MARKER_END}`;\n\n if (!existsSync(filePath)) {\n await writeFile(filePath, wrapped + \"\\n\", \"utf-8\");\n return;\n }\n\n const existing = await readFile(filePath, \"utf-8\");\n const startIdx = existing.indexOf(MARKER_START);\n const endIdx = existing.indexOf(MARKER_END);\n\n // Treat missing or unpaired marker as \"no markers\" — append section\n if (startIdx === -1 || endIdx === -1) {\n const separator = existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n await writeFile(filePath, existing + separator + wrapped + \"\\n\", \"utf-8\");\n return;\n }\n\n const before = existing.slice(0, startIdx);\n const after = existing.slice(endIdx + MARKER_END.length);\n await writeFile(filePath, before + wrapped + after, \"utf-8\");\n}\n","// Shared rendering helpers for all emitter modules\nimport type { ConventionRegistry, ConventionEntry } from \"../core/schema.js\";\n\n/**\n * Extract script commands from a filtered list of convention entries.\n * Shared by agents-md and renderConventionsBody.\n */\nexport function extractCommands(\n filtered: ConventionEntry[]\n): Array<{ scriptName: string; command: string }> {\n const commands: Array<{ scriptName: string; command: string }> = [];\n for (const entry of filtered) {\n const meta = entry.metadata;\n if (\n meta &&\n typeof meta[\"command\"] === \"string\" &&\n typeof meta[\"scriptName\"] === \"string\"\n ) {\n commands.push({\n scriptName: meta[\"scriptName\"] as string,\n command: meta[\"command\"] as string,\n });\n }\n }\n return commands;\n}\n\n/**\n * Check if a stack-category convention should appear in the Conventions section.\n * Stack entries that map to StackInfo fields (language, framework, etc.) are already\n * shown in the Stack section. Others (e.g. \"TypeScript strict mode\") are convention-worthy.\n */\nfunction isStackConventionWorthy(\n entry: ConventionEntry,\n stack: ConventionRegistry[\"stack\"]\n): boolean {\n if (entry.category !== \"stack\") return false;\n const meta = entry.metadata ?? {};\n // Already represented in Stack section via StackInfo fields\n if (typeof meta[\"language\"] === \"string\" && stack.language !== \"unknown\") return false;\n if (typeof meta[\"framework\"] === \"string\" && stack.framework) return false;\n if (typeof meta[\"buildTool\"] === \"string\" && stack.buildTool) return false;\n if (typeof meta[\"packageManager\"] === \"string\" && stack.packageManager) return false;\n // Script entries are shown in Commands section\n if (typeof meta[\"scriptName\"] === \"string\") return false;\n // Everything else (strict mode, compiler options, etc.) is convention-worthy\n return true;\n}\n\n/**\n * Check if a convention entry is redundant with the Stack or Commands sections.\n * - \"Test runner: X\" in testing category duplicates Stack > Test Runner\n * - Entries with metadata.scriptName duplicate the Commands section\n */\nexport function isRedundantConvention(entry: ConventionEntry): boolean {\n if (entry.category === \"testing\" && entry.pattern.startsWith(\"Test runner:\")) {\n return true;\n }\n if (entry.metadata && typeof entry.metadata[\"scriptName\"] === \"string\") {\n return true;\n }\n return false;\n}\n\n/**\n * Render conventions body as markdown lines.\n * Used by cursor-mdc, skill-md, rulesync-md, ruler-md, copilot-md.\n */\nexport function renderConventionsBody(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string[] {\n const filtered = registry.conventions.filter(\n (c) => c.confidence >= confidenceThreshold\n );\n\n const lines: string[] = [];\n\n // Stack section\n const s = registry.stack;\n const hasStack =\n s.language !== \"unknown\" ||\n Boolean(s.framework) ||\n Boolean(s.buildTool) ||\n Boolean(s.packageManager) ||\n Boolean(s.testRunner);\n\n if (hasStack) {\n lines.push(\"## Stack\");\n if (s.language !== \"unknown\") lines.push(`- Language: ${s.language}`);\n if (s.framework) lines.push(`- Framework: ${s.framework}`);\n if (s.buildTool) lines.push(`- Build: ${s.buildTool}`);\n if (s.packageManager) lines.push(`- Package Manager: ${s.packageManager}`);\n if (s.testRunner) lines.push(`- Test Runner: ${s.testRunner}`);\n lines.push(\"\");\n }\n\n // Architecture section\n const a = registry.architecture;\n const hasArchitecture =\n Boolean(a.pattern) || (a.layers?.length ?? 0) > 0;\n\n if (hasArchitecture) {\n lines.push(\"## Architecture\");\n if (a.pattern) lines.push(`- Pattern: ${a.pattern}`);\n if (a.layers && a.layers.length > 0) {\n lines.push(`- Layers: ${a.layers.join(\", \")}`);\n }\n lines.push(\"\");\n }\n\n // Conventions section — group by category, exclude architecture and redundant entries.\n // Stack entries without scriptName are kept (e.g. \"TypeScript strict mode enabled\").\n const categoryMap = new Map<string, string[]>();\n for (const entry of filtered) {\n if (entry.category === \"architecture\") continue;\n if (entry.category === \"stack\" && !isStackConventionWorthy(entry, s)) continue;\n if (isRedundantConvention(entry)) continue;\n const list = categoryMap.get(entry.category) ?? [];\n list.push(entry.pattern);\n categoryMap.set(entry.category, list);\n }\n\n if (categoryMap.size > 0) {\n lines.push(\"## Conventions\");\n for (const [category, patterns] of categoryMap) {\n for (const pattern of patterns) {\n lines.push(`- **${category}**: ${pattern}`);\n }\n }\n lines.push(\"\");\n }\n\n // Commands section — from conventions with metadata.command + metadata.scriptName\n const commands = extractCommands(filtered);\n\n if (commands.length > 0) {\n lines.push(\"## Commands\");\n for (const cmd of commands) {\n lines.push(`- \\`${cmd.scriptName}\\`: \\`${cmd.command}\\``);\n }\n lines.push(\"\");\n }\n\n return lines;\n}\n","import type { ConventionRegistry, ConventionEntry } from \"../core/schema.js\";\nimport { isRedundantConvention } from \"./render-helpers.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ConventionGroup {\n category: string;\n entries: ConventionEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Data prep\n// ---------------------------------------------------------------------------\n\nfunction prepData(registry: ConventionRegistry, confidenceThreshold: number) {\n const filtered = registry.conventions.filter(\n (c) => c.confidence >= confidenceThreshold\n );\n\n // Group by category, excluding architecture and entries redundant with Stack/Commands.\n // Stack entries not covered by StackInfo fields (e.g. \"TypeScript strict mode\") are kept.\n const categoryMap = new Map<string, ConventionEntry[]>();\n for (const entry of filtered) {\n if (entry.category === \"architecture\") continue;\n if (entry.category === \"stack\") {\n const meta = entry.metadata ?? {};\n // Skip entries already rendered in the Stack section\n if (typeof meta[\"language\"] === \"string\") continue;\n if (typeof meta[\"framework\"] === \"string\") continue;\n if (typeof meta[\"buildTool\"] === \"string\") continue;\n if (typeof meta[\"packageManager\"] === \"string\") continue;\n if (typeof meta[\"scriptName\"] === \"string\") continue;\n }\n if (isRedundantConvention(entry)) continue;\n const list = categoryMap.get(entry.category) ?? [];\n list.push(entry);\n categoryMap.set(entry.category, list);\n }\n\n const conventionGroups: ConventionGroup[] = [];\n for (const [category, entries] of categoryMap) {\n conventionGroups.push({ category, entries });\n }\n\n const hasStack =\n registry.stack.language !== \"unknown\" ||\n Boolean(registry.stack.framework) ||\n Boolean(registry.stack.buildTool) ||\n Boolean(registry.stack.packageManager) ||\n Boolean(registry.stack.testRunner);\n\n const hasArchitecture =\n Boolean(registry.architecture.pattern) ||\n (registry.architecture.layers?.length ?? 0) > 0;\n\n return {\n stack: registry.stack,\n architecture: registry.architecture,\n conventionGroups,\n hasStack,\n hasArchitecture,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Renderer\n// ---------------------------------------------------------------------------\n\nexport function renderClaudeMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const data = prepData(registry, confidenceThreshold);\n const lines: string[] = [];\n\n lines.push(\"# Project Context\");\n\n // Stack section\n if (data.hasStack) {\n lines.push(\"\");\n lines.push(\"## Stack\");\n if (data.stack.language !== \"unknown\") {\n lines.push(`- Language: ${data.stack.language}`);\n }\n if (data.stack.framework) lines.push(`- Framework: ${data.stack.framework}`);\n if (data.stack.buildTool) lines.push(`- Build: ${data.stack.buildTool}`);\n if (data.stack.packageManager) lines.push(`- Package Manager: ${data.stack.packageManager}`);\n if (data.stack.testRunner) lines.push(`- Test Runner: ${data.stack.testRunner}`);\n }\n\n // Conventions section\n if (data.conventionGroups.length > 0) {\n lines.push(\"\");\n lines.push(\"## Conventions\");\n for (const group of data.conventionGroups) {\n for (const entry of group.entries) {\n lines.push(`- **${group.category}**: ${entry.pattern}`);\n }\n }\n }\n\n // Architecture section\n if (data.hasArchitecture) {\n lines.push(\"\");\n lines.push(\"## Architecture\");\n if (data.architecture.pattern) {\n lines.push(`- Pattern: ${data.architecture.pattern}`);\n }\n if (data.architecture.layers && data.architecture.layers.length > 0) {\n lines.push(`- Layers: ${data.architecture.layers.join(\", \")}`);\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","import type { ConventionRegistry, ConventionEntry } from \"../core/schema.js\";\nimport { extractCommands } from \"./render-helpers.js\";\n\n// ---------------------------------------------------------------------------\n// Data prep\n// ---------------------------------------------------------------------------\n\nfunction prepData(registry: ConventionRegistry, confidenceThreshold: number) {\n const filtered = registry.conventions.filter(\n (c) => c.confidence >= confidenceThreshold\n );\n\n const commands = extractCommands(filtered);\n\n const byCategory = (cat: string): ConventionEntry[] =>\n filtered.filter((c) => c.category === cat);\n\n const testingConventions = byCategory(\"testing\");\n const namingConventions = byCategory(\"naming\");\n const importConventions = byCategory(\"imports\");\n const gitConventions = filtered.filter(\n (c) =>\n c.pattern.toLowerCase().includes(\"git\") ||\n (c.category === \"other\" && c.pattern.toLowerCase().includes(\"commit\"))\n );\n\n const hasTesting =\n Boolean(registry.stack.testRunner) || testingConventions.length > 0;\n\n const hasProjectStructure =\n Boolean(registry.architecture.pattern) ||\n (registry.architecture.layers?.length ?? 0) > 0;\n\n const hasCodeStyle =\n namingConventions.length > 0 || importConventions.length > 0;\n\n return {\n commands,\n testRunner: registry.stack.testRunner ?? null,\n testingConventions,\n namingConventions,\n importConventions,\n gitConventions,\n architecture: registry.architecture,\n hasTesting,\n hasProjectStructure,\n hasCodeStyle,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Renderer\n// ---------------------------------------------------------------------------\n\nexport function renderAgentsMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const data = prepData(registry, confidenceThreshold);\n const lines: string[] = [];\n\n lines.push(\"# AGENTS.md\");\n\n // Commands section\n if (data.commands.length > 0) {\n lines.push(\"\");\n lines.push(\"## Commands\");\n for (const cmd of data.commands) {\n lines.push(`- \\`${cmd.scriptName}\\`: \\`${cmd.command}\\``);\n }\n }\n\n // Testing section\n if (data.hasTesting) {\n lines.push(\"\");\n lines.push(\"## Testing\");\n if (data.testRunner) lines.push(`- Test runner: ${data.testRunner}`);\n for (const entry of data.testingConventions) {\n lines.push(`- ${entry.pattern}`);\n }\n }\n\n // Project Structure section\n if (data.hasProjectStructure) {\n lines.push(\"\");\n lines.push(\"## Project Structure\");\n if (data.architecture.pattern) {\n lines.push(`- Architecture: ${data.architecture.pattern}`);\n }\n if (data.architecture.layers && data.architecture.layers.length > 0) {\n lines.push(`- Layers: ${data.architecture.layers.join(\", \")}`);\n }\n }\n\n // Code Style section\n if (data.hasCodeStyle) {\n lines.push(\"\");\n lines.push(\"## Code Style\");\n for (const entry of data.namingConventions) {\n lines.push(`- **naming**: ${entry.pattern}`);\n }\n for (const entry of data.importConventions) {\n lines.push(`- **imports**: ${entry.pattern}`);\n }\n }\n\n // Git Workflow section (optional)\n if (data.gitConventions.length > 0) {\n lines.push(\"\");\n lines.push(\"## Git Workflow\");\n for (const entry of data.gitConventions) {\n lines.push(`- ${entry.pattern}`);\n }\n }\n\n // Boundaries section (always present)\n lines.push(\"\");\n lines.push(\"## Boundaries\");\n lines.push(\"- Do not modify auto-generated sections between ez-context markers\");\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","import yaml from \"js-yaml\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a Cursor MDC rule file (.cursor/rules/ez-context.mdc).\n *\n * Format: YAML frontmatter + markdown body.\n * - description: shown in Cursor UI\n * - globs: empty string (not null/omitted) per Cursor docs\n * - alwaysApply: true ensures conventions are always in context\n */\nexport function renderCursorMdc(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const frontmatter = yaml\n .dump({\n description: \"Project conventions extracted by ez-context\",\n globs: \"\",\n alwaysApply: true,\n })\n .trimEnd();\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line added by renderConventionsBody\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n const body = bodyLines.join(\"\\n\");\n\n return `---\\n${frontmatter}\\n---\\n\\n${body}\\n`;\n}\n","import type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a GitHub Copilot instructions file (.github/copilot-instructions.md).\n *\n * Format: Plain markdown, no YAML frontmatter.\n * GitHub Copilot reads the entire file; HTML comment markers are used\n * for idempotent updates via writeWithMarkers.\n */\nexport function renderCopilotMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const lines: string[] = [];\n\n lines.push(\"<!-- Generated by ez-context. Do not edit between markers. -->\");\n lines.push(\"\");\n lines.push(\"# Copilot Instructions\");\n lines.push(\"\");\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n lines.push(...bodyLines);\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","import yaml from \"js-yaml\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a SKILL.md module file (.skills/ez-context/SKILL.md).\n *\n * Format: YAML frontmatter + markdown body.\n * - name: must match directory name (ez-context)\n * - description: max 1024 chars, describes what AND when to use\n * Body stays under 5000 tokens (~3750 words) as per SKILL.md spec.\n */\nexport function renderSkillMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const description =\n \"Project conventions and coding standards for this codebase. \" +\n \"Use when writing new code, reviewing patterns, or understanding project architecture.\";\n\n const frontmatter = yaml\n .dump({\n name: \"ez-context\",\n description,\n })\n .trimEnd();\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n const body = bodyLines.join(\"\\n\");\n\n return `---\\n${frontmatter}\\n---\\n\\n${body}\\n`;\n}\n","import yaml from \"js-yaml\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a Rulesync rule file (.rulesync/rules/ez-context.md).\n *\n * Format: YAML frontmatter + markdown body.\n * - targets: specifies which AI tools receive this rule\n * ez-context writes INTO .rulesync/rules/; Rulesync distributes to tools.\n */\nexport function renderRulesyncMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const frontmatter = yaml\n .dump({\n description: \"Project conventions extracted by ez-context\",\n targets: [\"cursor\", \"copilot\", \"windsurf\"],\n })\n .trimEnd();\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n const body = bodyLines.join(\"\\n\");\n\n return `---\\n${frontmatter}\\n---\\n\\n${body}\\n`;\n}\n","import type { ConventionRegistry } from \"../core/schema.js\";\nimport { renderConventionsBody } from \"./render-helpers.js\";\n\n/**\n * Render a Ruler rule file (.ruler/ez-context.md).\n *\n * Format: Plain markdown, no YAML frontmatter.\n * Ruler recursively discovers all .md files in .ruler/ and distributes them.\n * ez-context writes a single .ruler/ez-context.md as an additive conventions file.\n */\nexport function renderRulerMd(\n registry: ConventionRegistry,\n confidenceThreshold: number\n): string {\n const lines: string[] = [];\n\n lines.push(\"# Project Conventions (ez-context)\");\n lines.push(\"\");\n\n const bodyLines = renderConventionsBody(registry, confidenceThreshold);\n // Remove trailing empty line\n while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === \"\") {\n bodyLines.pop();\n }\n lines.push(...bodyLines);\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","import path from \"node:path\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport type { ConventionRegistry } from \"../core/schema.js\";\nimport type { EmitOptions, EmitResult, OutputFormat } from \"./types.js\";\nimport { writeWithMarkers } from \"./writer.js\";\nimport { renderClaudeMd } from \"./claude-md.js\";\nimport { renderAgentsMd } from \"./agents-md.js\";\nimport { renderCursorMdc } from \"./cursor-mdc.js\";\nimport { renderCopilotMd } from \"./copilot-md.js\";\nimport { renderSkillMd } from \"./skill-md.js\";\nimport { renderRulesyncMd } from \"./rulesync-md.js\";\nimport { renderRulerMd } from \"./ruler-md.js\";\n\nexport { renderClaudeMd } from \"./claude-md.js\";\nexport { renderAgentsMd } from \"./agents-md.js\";\nexport { renderCursorMdc } from \"./cursor-mdc.js\";\nexport { renderCopilotMd } from \"./copilot-md.js\";\nexport { renderSkillMd } from \"./skill-md.js\";\nexport { renderRulesyncMd } from \"./rulesync-md.js\";\nexport { renderRulerMd } from \"./ruler-md.js\";\nexport type { OutputFormat } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Format emitter registry\n// ---------------------------------------------------------------------------\n\ntype WriteStrategy = \"markers\" | \"direct\";\n\ninterface FormatEmitterEntry {\n render: (registry: ConventionRegistry, threshold: number) => string;\n filename: string;\n strategy: WriteStrategy;\n}\n\nexport const FORMAT_EMITTER_MAP: Record<OutputFormat, FormatEmitterEntry> = {\n claude: {\n render: renderClaudeMd,\n filename: \"CLAUDE.md\",\n strategy: \"markers\",\n },\n agents: {\n render: renderAgentsMd,\n filename: \"AGENTS.md\",\n strategy: \"markers\",\n },\n cursor: {\n render: renderCursorMdc,\n filename: path.join(\".cursor\", \"rules\", \"ez-context.mdc\"),\n strategy: \"direct\",\n },\n copilot: {\n render: renderCopilotMd,\n filename: path.join(\".github\", \"copilot-instructions.md\"),\n strategy: \"markers\",\n },\n skills: {\n render: renderSkillMd,\n filename: path.join(\".skills\", \"ez-context\", \"SKILL.md\"),\n strategy: \"direct\",\n },\n rulesync: {\n render: renderRulesyncMd,\n filename: path.join(\".rulesync\", \"rules\", \"ez-context.md\"),\n strategy: \"markers\",\n },\n ruler: {\n render: renderRulerMd,\n filename: path.join(\".ruler\", \"ez-context.md\"),\n strategy: \"direct\",\n },\n};\n\n// ---------------------------------------------------------------------------\n// emit()\n// ---------------------------------------------------------------------------\n\n/**\n * Emit output files for the given ConventionRegistry.\n *\n * Defaults to [\"claude\", \"agents\"] for backward compatibility.\n * In dryRun mode, returns rendered content without writing any files.\n */\nexport async function emit(\n registry: ConventionRegistry,\n options: EmitOptions\n): Promise<EmitResult> {\n const threshold = options.confidenceThreshold ?? 0.7;\n const formats: OutputFormat[] = options.formats ?? [\"claude\", \"agents\"];\n\n // Render all requested formats\n const rendered: Record<string, string> = {};\n for (const format of formats) {\n const entry = FORMAT_EMITTER_MAP[format];\n rendered[format] = entry.render(registry, threshold);\n }\n\n // Backward compat aliases\n const claudeMd = rendered[\"claude\"] ?? \"\";\n const agentsMd = rendered[\"agents\"] ?? \"\";\n\n if (options.dryRun) {\n return { rendered, claudeMd, agentsMd, filesWritten: [] };\n }\n\n // Write files\n const filesWritten: string[] = [];\n for (const format of formats) {\n const entry = FORMAT_EMITTER_MAP[format];\n const filePath = path.join(options.outputDir, entry.filename);\n\n await mkdir(path.dirname(filePath), { recursive: true });\n if (entry.strategy === \"direct\") {\n await writeFile(filePath, rendered[format]!, \"utf-8\");\n } else {\n await writeWithMarkers(filePath, rendered[format]!);\n }\n\n filesWritten.push(filePath);\n }\n\n return { rendered, claudeMd, agentsMd, filesWritten };\n}\n"],"mappings":"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"}