Skip to content

Instantly share code, notes, and snippets.

@vishal-h
Last active July 20, 2025 09:24
Show Gist options
  • Save vishal-h/6bb7c3664c514b803f1580c5bec31dee to your computer and use it in GitHub Desktop.
Save vishal-h/6bb7c3664c514b803f1580c5bec31dee to your computer and use it in GitHub Desktop.
Elixir Mix Task: LLM Ingest - Generate LLM-friendly code exports. [vibe coded with Claude Sonnet 4]

Mix Task: LLM Ingest

A powerful Mix task for generating LLM-friendly code ingestion files from Elixir projects. Creates well-structured markdown files containing your source code, making it easy to share context with Large Language Models for code analysis, debugging, or feature development.

✨ Features

  • 🎯 Feature-based filtering - Focus on specific parts of your codebase
  • 📁 Smart directory traversal - ASCII tree structure with proper organization
  • 🎨 Markdown output - Clean formatting with syntax highlighting
  • 🚫 Intelligent exclusions - Built-in patterns for common artifacts
  • 📝 Gitignore integration - Automatically respects your .gitignore
  • 🔧 Highly configurable - Custom include/exclude patterns
  • 🤖 LLM-optimized - Includes context notes for better AI understanding

🚀 Quick Start

1. Install the Task

Copy llm_ingest.ex to your project:

mkdir -p lib/mix/tasks
# Copy llm_ingest.ex to lib/mix/tasks/

2. Create Feature Configuration

Create llm_features.exs in your project root:

%{
  "auth" => %{
    include: "lib/auth/**,test/auth/**,priv/repo/migrations/*_auth_*",
    exclude: "**/*_test.exs"
  },
  "api" => %{
    include: "lib/api/**,lib/schemas/**,test/api/**",
    exclude: "lib/api/legacy/**"
  },
  "core" => %{
    include: "lib/application.ex,lib/core/**,mix.exs"
  }
}

3. Generate LLM-Ready Files

# Generate for specific feature
mix llm_ingest --feature=auth

# Generate for entire project
mix llm_ingest

# Custom patterns
mix llm_ingest --include="lib/specific/**" --exclude="**/*_old.ex"

📖 Usage Examples

Basic Commands

# Feature-based ingestion (recommended)
mix llm_ingest --feature=auth
# → Creates: doc/llm-ingest-auth.md

# Full project analysis
mix llm_ingest
# → Creates: doc/llm-ingest.md

# Custom patterns
mix llm_ingest --include="lib/payments/**,test/payments/**"
# → Creates: doc/llm-ingest.md

# Custom output location
mix llm_ingest --feature=api --output=analysis/api-deep-dive.md

Advanced Usage

# Combine feature with additional excludes
mix llm_ingest --feature=auth --exclude="**/legacy_auth.ex"

# Disable gitignore integration
mix llm_ingest --no-gitignore

# Different root directory
mix llm_ingest /path/to/other/project --feature=core

⚙️ Configuration

Feature Configuration File (llm_features.exs)

%{
  "auth" => %{
    include: "lib/auth/**,test/auth/**,priv/repo/migrations/*_create_users*",
    exclude: "**/*_test.exs,lib/auth/legacy/**"
  },
  "api" => %{
    include: "lib/api/**,lib/schemas/**,lib/*_web/controllers/api/**",
    exclude: "lib/api/v1/**"  # Exclude old API versions
  },
  "frontend" => %{
    include: "assets/**,lib/*_web/**,test/*_web/**",
    exclude: "assets/node_modules/**"
  },
  "payments" => %{
    include: "lib/payments/**,lib/billing/**,test/payments/**",
    exclude: "lib/payments/legacy/**,**/*_test.exs"
  },
  "complete" => %{
    include: "lib/**,config/**,mix.exs",
    exclude: "**/*_test.exs,lib/legacy/**"
  }
}

Pattern Syntax

  • ** - Matches any number of directories (recursive)
  • * - Matches any characters except /
  • lib/auth/** - All files under lib/auth/ recursively
  • *.ex - All .ex files
  • **/*_test.exs - All test files recursively

📋 Command Line Options

Option Description Example
--feature Use predefined feature configuration --feature=auth
--include Include files matching patterns (comma-separated) --include="lib/auth/**,test/auth/**"
--exclude Exclude files matching patterns (comma-separated) --exclude="**/*_test.exs"
--output Specify output file path --output=analysis/deep-dive.md
--no-gitignore Disable automatic gitignore integration --no-gitignore

📄 Output Structure

The generated markdown file includes:

# ProjectName
Project description from mix.exs

---

## Feature: auth
**Include patterns:** `lib/auth/**`, `test/auth/**`
**Exclude patterns:** `**/*_test.exs`

---

## Notes for AI Analysis
**Authentication Feature Context:**
- Look for authentication pipelines, plugs, and middleware
- Check for token generation, validation, and refresh logic
...

**General Elixir Project Reading Guide:**
- Application structure explanations
- Module conventions and patterns
- Key Elixir idioms and practices

---

## Project Structure

./lib/ ├── auth/ │ ├── guardian.ex │ ├── pipeline.ex │ └── user_manager.ex └── auth.ex


---

## Files

---

### lib/auth/guardian.ex
```elixir
defmodule MyApp.Auth.Guardian do
  # Your code with syntax highlighting
end

## 🎯 LLM Integration Tips

### Best Practices

1. **Feature-Focused Analysis**
   ```bash
   # For authentication work
   mix llm_ingest --feature=auth
   
   # For API development  
   mix llm_ingest --feature=api
  1. Include Relevant Tests

    "auth" => %{
      include: "lib/auth/**,test/auth/**",  # Include tests for context
      exclude: "test/fixtures/**"           # But exclude fixtures
    }
  2. Exclude Legacy Code

    "payments" => %{
      include: "lib/payments/**",
      exclude: "lib/payments/legacy/**,lib/payments/deprecated/**"
    }

Effective Prompts

When sharing generated files with LLMs:

I'm working on the authentication feature of my Elixir app. 
Here's the relevant code structure and implementation:

[Paste generated markdown content]

Please help me:
1. Review the authentication pipeline for security issues
2. Suggest improvements for token handling
3. Identify any missing error cases

🔧 Project Setup

Add to Your Project

  1. Copy the task file:

    mkdir -p lib/mix/tasks
    curl -o lib/mix/tasks/llm_ingest.ex https://gist.github.com/[gist-id]/raw/llm_ingest.ex
  2. Create feature configuration:

    # Copy example and customize
    curl -o llm_features.exs https://gist.github.com/[gist-id]/raw/llm_features.example.exs
  3. Update .gitignore:

    # LLM ingest files
    doc/llm-ingest*.md
  4. Compile and use:

    mix compile
    mix llm_ingest --feature=auth

Project Requirements

  • Elixir: 1.14+ (uses modern Path and File functions)
  • Mix: Standard Elixir installation
  • Dependencies: None (pure Elixir/OTP)

🛠️ Customization

Adding New File Types

Edit the write_files_content function to add syntax highlighting for new extensions:

language = case Path.extname(file) do
  ".ex" -> "elixir"
  ".exs" -> "elixir"
  ".heex" -> "html"      # Add Phoenix templates
  ".leex" -> "html"      # Add LiveView templates
  ".eex" -> "html"       # Add EEx templates
  # ... your custom extensions
  _ -> ""
end

Custom Default Exclusions

Modify the @default_excludes list for your needs:

@default_excludes [
  # Add your custom exclusions
  "priv/static/**",
  "coverage/**",
  # ... existing exclusions
]

Feature-Specific Notes

Add custom notes in the get_feature_specific_notes function:

defp get_feature_specific_notes(feature) do
  case feature do
    "my_custom_feature" -> """
**My Custom Feature Context:**
- Look for specific patterns in your domain
- Check for custom implementations
"""
    # ... existing features
  end
end

🤝 Contributing

This is a community-driven tool! Ways to contribute:

  1. ⭐ Star this gist if you find it useful
  2. 💬 Comment with suggestions, improvements, or issues
  3. 🍴 Fork and customize for your specific needs
  4. 📢 Share with the Elixir community

Common Improvements

  • Add support for more file types
  • Enhance pattern matching capabilities
  • Improve LLM context notes
  • Add integration with other tools

📚 Real-World Examples

Phoenix Web Application

%{
  "auth" => %{
    include: "lib/my_app/accounts/**,lib/my_app_web/controllers/auth_*,test/my_app/accounts/**",
    exclude: "**/*_test.exs"
  },
  "live_views" => %{
    include: "lib/my_app_web/live/**,assets/js/**",
    exclude: "assets/node_modules/**"
  },
  "api" => %{
    include: "lib/my_app_web/controllers/api/**,lib/my_app_web/views/api/**",
    exclude: "lib/my_app_web/controllers/api/v1/**"  # Old version
  }
}

GenServer/OTP Application

%{
  "workers" => %{
    include: "lib/my_app/workers/**,lib/my_app/supervisor.ex",
    exclude: "**/*_test.exs"
  },
  "core" => %{
    include: "lib/my_app/application.ex,lib/my_app/core/**,config/**"
  }
}

Umbrella Project

%{
  "web_app" => %{
    include: "apps/my_app_web/**",
    exclude: "apps/my_app_web/assets/node_modules/**"
  },
  "business_logic" => %{
    include: "apps/my_app/**",
    exclude: "**/*_test.exs"
  }
}

🐛 Troubleshooting

Empty Output

Problem: Generated file only contains headers, no files

Solutions:

  1. Check include patterns match actual file paths
  2. Verify files aren't excluded by default patterns
  3. Test with simpler patterns first: --include="lib/**"

Pattern Not Matching

Problem: Expected files not included

Solutions:

  1. Use forward slashes / even on Windows
  2. Remember ** for recursive, * for single level
  3. Test patterns: --include="lib/auth/**" --exclude=""

Permission Errors

Problem: Cannot read files or write output

Solutions:

  1. Check file permissions on source directories
  2. Ensure doc/ directory is writable
  3. Run with appropriate permissions

Large Output Files

Problem: Generated files are too large for LLM context

Solutions:

  1. Use more specific include patterns
  2. Exclude test files: --exclude="**/*_test.exs"
  3. Focus on specific features rather than entire project

📜 License

This Mix task is provided as-is for educational and development purposes. Feel free to modify and distribute according to your project's needs.


Made with ❤️ for the Elixir community

Star this gist if it helps you build better software with AI assistance!

# LLM Ingest Feature Configuration
# Copy this file to your project root as 'llm_features.exs' and customize for your needs
%{
# Authentication and User Management
"auth" => %{
include: "lib/auth/**,lib/accounts/**,test/auth/**,test/accounts/**,priv/repo/migrations/*_create_users*,priv/repo/migrations/*_auth_*",
exclude: "**/*_test.exs,lib/auth/legacy/**"
},
# API Endpoints and Controllers
"api" => %{
include: "lib/api/**,lib/schemas/**,lib/*_web/controllers/api/**,lib/*_web/views/api/**,test/api/**",
exclude: "lib/api/v1/**,lib/api/deprecated/**"
},
# Frontend/LiveView Components
"frontend" => %{
include: "assets/**,lib/*_web/live/**,lib/*_web/components/**,lib/*_web/templates/**,test/*_web/**",
exclude: "assets/node_modules/**,assets/vendor/**"
},
# Payment and Billing System
"payments" => %{
include: "lib/payments/**,lib/billing/**,test/payments/**,test/billing/**,priv/repo/migrations/*_payment*,priv/repo/migrations/*_billing*",
exclude: "lib/payments/legacy/**,**/*_test.exs"
},
# Core Business Logic
"core" => %{
include: "lib/core/**,lib/application.ex,lib/**/supervisor.ex,config/**,mix.exs",
exclude: "config/dev.exs,config/test.exs"
},
# Database and Schemas
"database" => %{
include: "lib/schemas/**,lib/**/schema.ex,priv/repo/**,test/schemas/**",
exclude: "priv/repo/seeds/**"
},
# GenServers and Workers
"workers" => %{
include: "lib/workers/**,lib/**/worker.ex,lib/**/server.ex,lib/application.ex",
exclude: "**/*_test.exs"
},
# Phoenix Web Layer
"web" => %{
include: "lib/*_web/**,assets/**,config/config.exs",
exclude: "assets/node_modules/**,lib/*_web/templates/layout/**"
},
# Testing Infrastructure
"testing" => %{
include: "test/**,config/test.exs",
exclude: "test/fixtures/**"
},
# Configuration and Setup
"config" => %{
include: "config/**,mix.exs,.formatter.exs,priv/repo/migrations/**",
exclude: "config/dev.secret.exs,config/prod.secret.exs"
},
# Specific Feature Examples:
# E-commerce Product Management
"products" => %{
include: "lib/products/**,lib/catalog/**,test/products/**,priv/repo/migrations/*_product*",
exclude: "**/*_test.exs"
},
# Real-time Features (Phoenix PubSub, LiveView)
"realtime" => %{
include: "lib/**/pubsub.ex,lib/*_web/live/**,lib/**/presence.ex,assets/js/live_socket.js",
exclude: "**/*_test.exs"
},
# File Upload and Media
"uploads" => %{
include: "lib/uploads/**,lib/media/**,priv/static/uploads/**,config/uploads.exs",
exclude: "priv/static/uploads/test/**"
},
# Email and Notifications
"notifications" => %{
include: "lib/notifications/**,lib/mailer/**,lib/**/email.ex,priv/templates/**",
exclude: "**/*_test.exs"
},
# Admin Interface
"admin" => %{
include: "lib/*_web/live/admin/**,lib/admin/**,assets/css/admin.css",
exclude: "**/*_test.exs"
},
# Complete Project (Use sparingly - may be too large for LLMs)
"complete" => %{
include: "lib/**,config/**,mix.exs,assets/**",
exclude: "**/*_test.exs,assets/node_modules/**,_build/**,deps/**"
}
}
# Usage Examples:
# mix llm_ingest --feature=auth # Focus on authentication
# mix llm_ingest --feature=api # API development
# mix llm_ingest --feature=frontend # UI/LiveView work
# mix llm_ingest --feature=payments # Payment system
# mix llm_ingest --feature=core # Core business logic
# Pattern Syntax:
# ** - Recursive directory match
# * - Single-level wildcard
# lib/** - All files under lib/ recursively
# *.ex - All .ex files
# **/*_test.exs - All test files recursively
# Tips:
# - Start with specific features rather than "complete"
# - Include related tests for better context
# - Exclude legacy/deprecated code
# - Use descriptive feature names for your team
defmodule Mix.Tasks.LlmIngest do
use Mix.Task
@default_excludes [
# Version control
".git/",
# Build artifacts
"_build/", "deps/", "node_modules/", "*.beam", "*.o", "*.so", "*.dylib", "*.a",
# Dependency files
"mix.lock", "package-lock.json", "yarn.lock", "Gemfile.lock",
# System files
".DS_Store", "Thumbs.db",
# Editor files
"*.swp", "*.swo", "*.swn", ".idea/", ".vscode/",
# Elixir specific
"erl_crash.dump", "*.ez",
# Documentation output (to avoid including generated llm-ingest files)
"doc/llm-ingest*.md"
]
@separator "\n---\n"
@line_ending "\n"
@impl true
def run(args) do
{opts, args, _} = OptionParser.parse(args,
switches: [
exclude: :string,
include: :string,
output: :string,
"no-gitignore": :boolean,
feature: :string
]
)
root = Path.expand(List.first(args) || ".")
# Load feature configuration if specified
{feature_include, feature_exclude} = load_feature_config(opts[:feature], root)
exclude_patterns = parse_patterns(opts[:exclude]) || []
include_patterns = parse_patterns(opts[:include]) || feature_include
# Generate output filename based on feature
default_filename = case opts[:feature] do
nil -> "llm-ingest.md"
feature -> "llm-ingest-#{feature}.md"
end
output_file = opts[:output] || default_filename
# Ensure doc directory exists and prepend it unless output already includes a path
output_file = case Path.dirname(output_file) do
"." -> Path.join("doc", output_file)
_ -> output_file
end
# Create doc directory if it doesn't exist
File.mkdir_p!(Path.dirname(output_file))
use_gitignore = !opts[:"no-gitignore"]
gitignore_patterns = if use_gitignore, do: read_gitignore_patterns(root), else: []
all_excludes = Enum.uniq(@default_excludes ++ exclude_patterns ++ feature_exclude ++ gitignore_patterns)
project_name = case Mix.Project.config()[:app] do
nil -> Path.basename(root)
app -> to_string(app)
end
description = Mix.Project.config()[:description] || "No description"
File.write!(output_file, "", [:raw])
write_section(output_file, "# #{project_name}\n#{description}")
# Add feature section if a feature was specified
if opts[:feature] do
feature_section = "## Feature: #{opts[:feature]}\n\n" <>
case feature_include do
nil -> "Feature configuration not found or invalid."
patterns ->
include_text = "**Include patterns:** `#{Enum.join(patterns, "`, `")}`\n\n"
exclude_text = case feature_exclude do
[] -> ""
excludes -> "**Exclude patterns:** `#{Enum.join(excludes, "`, `")}`\n\n"
end
include_text <> exclude_text
end
write_section(output_file, feature_section)
end
# Add folder structure section with markdown header
write_section(output_file, "## Project Structure\n\n```\n#{generate_folder_structure(root, all_excludes, include_patterns)}```")
# Add files section header
write_section(output_file, "## Files")
write_files_content(output_file, root, all_excludes, include_patterns)
Mix.shell().info("Generated #{output_file}")
end
defp load_feature_config(nil, _root), do: {nil, []}
defp load_feature_config(feature_name, root) do
config_file = Path.join(root, "llm_features.exs")
if File.exists?(config_file) do
try do
{features_config, _} = Code.eval_file(config_file)
case Map.get(features_config, feature_name) do
nil ->
Mix.shell().error("Feature '#{feature_name}' not found in llm_features.exs")
available_features = Map.keys(features_config) |> Enum.join(", ")
Mix.shell().info("Available features: #{available_features}")
{nil, []}
feature_config ->
include_patterns = parse_patterns(feature_config[:include])
exclude_patterns = parse_patterns(feature_config[:exclude]) || []
Mix.shell().info("Loaded feature '#{feature_name}' with #{length(include_patterns || [])} include patterns")
Mix.shell().info("Include patterns: #{inspect(include_patterns)}")
{include_patterns, exclude_patterns}
end
rescue
e ->
Mix.shell().error("Failed to load llm_features.exs: #{inspect(e)}")
{nil, []}
end
else
Mix.shell().error("Feature configuration file 'llm_features.exs' not found in project root")
Mix.shell().info("Create llm_features.exs with your feature definitions")
print_example_config()
{nil, []}
end
end
defp print_example_config do
example = """
Example llm_features.exs:
%{
"auth" => %{
include: "lib/auth/**,test/auth/**,priv/repo/migrations/*_auth_*",
exclude: "**/*_test.exs"
},
"api" => %{
include: "lib/api/**,lib/schemas/**,test/api/**"
},
"frontend" => %{
include: "assets/**,lib/*_web/**,test/*_web/**"
}
}
"""
Mix.shell().info(example)
end
defp read_gitignore_patterns(root) do
gitignore_path = Path.join(root, ".gitignore")
if File.exists?(gitignore_path) do
gitignore_path
|> File.read!()
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.reject(fn line ->
String.starts_with?(line, "#") || line == ""
end)
else
[]
end
end
defp parse_patterns(nil), do: nil
defp parse_patterns(str), do: String.split(str, ",") |> Enum.map(&String.trim/1)
defp write_section(output_file, content) do
File.write!(output_file, [content, @separator], [:append, :raw])
end
defp generate_folder_structure(root, exclude_patterns, include_patterns) do
relative_root = Path.relative_to_cwd(root)
"#{relative_root}/#{@line_ending}" <> build_tree(root, exclude_patterns, include_patterns, "", true)
end
defp build_tree(path, exclude_patterns, include_patterns, prefix, is_root) do
if File.dir?(path) do
children =
path
|> File.ls!()
|> Enum.reject(&should_exclude?(&1, path, exclude_patterns))
|> Enum.filter(&should_include?(&1, path, include_patterns))
|> Enum.sort()
# Debug output
if is_root and include_patterns do
all_children = File.ls!(path)
Mix.shell().info("Root directory children: #{inspect(all_children)}")
Mix.shell().info("After filtering: #{inspect(children)}")
end
children
|> Enum.with_index()
|> Enum.map_join("", fn {child, index} ->
is_last = index == length(children) - 1
full_path = Path.join(path, child)
current_prefix = if is_root, do: "", else: prefix
connector = if is_last, do: "└── ", else: "├── "
next_prefix = if is_last, do: " ", else: "│ "
if File.dir?(full_path) do
"#{current_prefix}#{connector}#{child}/#{@line_ending}" <>
build_tree(full_path, exclude_patterns, include_patterns, current_prefix <> next_prefix, false)
else
"#{current_prefix}#{connector}#{child}#{@line_ending}"
end
end)
else
""
end
end
defp should_exclude?(item, current_path, exclude_patterns) do
full_path = Path.join(current_path, item)
relative_path = Path.relative_to_cwd(full_path)
matches_any_pattern?(item, exclude_patterns) ||
matches_any_pattern?(relative_path, exclude_patterns)
end
defp should_include?(item, current_path, include_patterns) do
case include_patterns do
nil -> true
patterns ->
full_path = Path.join(current_path, item)
relative_path = Path.relative_to_cwd(full_path)
# Direct match
direct_match = matches_any_pattern?(item, patterns) ||
matches_any_pattern?(relative_path, patterns) ||
path_matches_glob?(relative_path, patterns)
# If it's a directory, check if any pattern could match files inside it
directory_could_contain_matches = File.dir?(full_path) &&
Enum.any?(patterns, fn pattern ->
pattern_could_match_under_directory?(pattern, relative_path)
end)
direct_match || directory_could_contain_matches
end
end
defp pattern_could_match_under_directory?(pattern, directory_path) do
cond do
# For patterns like "lib/dia/application.ex", check if "lib" could lead to this file
String.contains?(pattern, "/") ->
pattern_parts = String.split(pattern, "/")
directory_parts = String.split(directory_path, "/")
# Check if the directory path is a prefix of the pattern path
lists_have_common_prefix?(directory_parts, pattern_parts)
# For simple patterns without /, they could match files anywhere
true -> true
end
end
defp lists_have_common_prefix?([], _), do: true
defp lists_have_common_prefix?(_, []), do: false
defp lists_have_common_prefix?([h1 | t1], [h2 | t2]) when h1 == h2 do
lists_have_common_prefix?(t1, t2)
end
defp lists_have_common_prefix?([h1 | _], [h2 | _]) when h1 != h2, do: false
defp path_matches_glob?(path, patterns) do
Enum.any?(patterns, fn pattern ->
# Check if the path is under a directory that matches the pattern
cond do
String.contains?(pattern, "**") ->
# For patterns like "lib/dia/agent/**", check if path starts with "lib/dia/agent/"
base_pattern = String.replace(pattern, "/**", "")
String.starts_with?(path, base_pattern <> "/") || path == base_pattern
String.ends_with?(pattern, "/*") ->
# For patterns like "lib/dia/*", check if path is directly under that directory
base_pattern = String.slice(pattern, 0..-3//1)
String.starts_with?(path, base_pattern <> "/")
true ->
false
end
end)
end
defp matches_any_pattern?(item, patterns) when is_list(patterns) do
Enum.any?(patterns, fn pattern ->
try do
# Handle glob patterns with **
regex_pattern = cond do
String.contains?(pattern, "**") ->
# Convert ** to match any path depth
pattern
|> String.replace(~r/^\//, "") # Remove leading /
|> String.replace(".", "\\.")
|> String.replace("**", ".*")
|> String.replace("*", "[^/]*")
|> then(fn s -> "^#{s}$" end)
true ->
# Regular pattern matching
pattern
|> String.replace(~r/^\//, "") # Remove leading /
|> String.replace(".", "\\.")
|> String.replace("*", "[^/]*")
|> then(fn s ->
if String.ends_with?(s, "/") do
dir_part = if String.length(s) > 1, do: String.slice(s, 0..-2//1), else: s
"^#{dir_part}($|/.*)" # Directory match
else
"^#{s}$" # Exact file match
end
end)
end
Regex.match?(Regex.compile!(regex_pattern), item)
rescue
_ -> false # Skip invalid patterns
end
end)
end
defp matches_any_pattern?(_item, _patterns), do: false
defp write_files_content(output_file, root, exclude_patterns, include_patterns) do
root
|> list_files(exclude_patterns, include_patterns)
|> Enum.each(fn file ->
try do
content =
file
|> File.read!()
|> String.replace("\r\n", @line_ending)
|> String.replace("\r", @line_ending)
relative_path = Path.relative_to_cwd(file)
# Determine language for syntax highlighting
language = case Path.extname(file) do
".ex" -> "elixir"
".exs" -> "elixir"
".js" -> "javascript"
".ts" -> "typescript"
".py" -> "python"
".rb" -> "ruby"
".go" -> "go"
".rs" -> "rust"
".java" -> "java"
".c" -> "c"
".cpp" -> "cpp"
".h" -> "c"
".css" -> "css"
".scss" -> "scss"
".html" -> "html"
".xml" -> "xml"
".json" -> "json"
".yaml" -> "yaml"
".yml" -> "yaml"
".sql" -> "sql"
".sh" -> "bash"
".md" -> "markdown"
_ -> ""
end
write_section(output_file, "### #{relative_path}\n\n```#{language}\n#{content}\n```")
rescue
e ->
Mix.shell().error("Failed to read file #{file}: #{inspect(e)}")
end
end)
end
defp list_files(root, exclude_patterns, include_patterns) do
if File.dir?(root) do
try do
root
|> File.ls!()
|> Enum.reject(&should_exclude?(&1, root, exclude_patterns))
|> Enum.filter(&should_include?(&1, root, include_patterns))
|> Enum.flat_map(fn item ->
list_files(Path.join(root, item), exclude_patterns, include_patterns)
end)
rescue
e ->
Mix.shell().error("Failed to list directory #{root}: #{inspect(e)}")
[]
end
else
# Only include files that match include patterns (if specified) and don't match exclude patterns
relative_path = Path.relative_to_cwd(root)
basename = Path.basename(root)
should_include_file = should_include?(basename, Path.dirname(root), include_patterns) ||
(include_patterns && path_matches_glob?(relative_path, include_patterns))
should_exclude_file = should_exclude?(basename, Path.dirname(root), exclude_patterns)
if should_include_file && !should_exclude_file do
[root]
else
[]
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment