Packages
A config-driven dev tool for Elixir projects to manage AGENTS.md files and agent skills from dependencies
Current section
42 Versions
Jump to
Current section
42 Versions
Compare versions
6
files changed
+216
additions
-89
deletions
| @@ -12,6 +12,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline | |
| 12 12 | |
| 13 13 | <!-- changelog --> |
| 14 14 | |
| 15 | + ## v1.0.0-rc.2 (2026-02-07) |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | + |
| 20 | + ### Improvements: |
| 21 | + |
| 22 | + * reference mode for skill deps by Zach Daniel |
| 23 | + |
| 15 24 | ## v1.0.0-rc.1 (2026-02-07) |
| @@ -34,7 +34,7 @@ Or add `usage_rules` manually to your `mix.exs`: | |
| 34 34 | ```elixir |
| 35 35 | def deps do |
| 36 36 | [ |
| 37 | - {:usage_rules, "~> 0.2", only: [:dev]}, |
| 37 | + {:usage_rules, "~> 1.0", only: [:dev]}, |
| 38 38 | {:igniter, "~> 0.6", only: [:dev]} |
| 39 39 | ] |
| 40 40 | end |
| @@ -47,16 +47,40 @@ All configuration lives in your `mix.exs` project config. Add a `:usage_rules` k | |
| 47 47 | ```elixir |
| 48 48 | def project do |
| 49 49 | [ |
| 50 | - app: :my_app, |
| 51 | - # ... |
| 50 | + ... |
| 52 51 | usage_rules: usage_rules() |
| 53 52 | ] |
| 54 53 | end |
| 55 54 | |
| 56 55 | defp usage_rules do |
| 56 | + # Example for those using claude. |
| 57 57 | [ |
| 58 | - file: "AGENTS.md", |
| 59 | - usage_rules: :all |
| 58 | + file: "CLAUDE.md", |
| 59 | + # rules to include directly in CLAUDE.md |
| 60 | + usage_rules: ["usage_rules:all"], |
| 61 | + skills: [ |
| 62 | + location: ".claude/skills", |
| 63 | + # build skills that combine multiple usage rules |
| 64 | + build: [ |
| 65 | + "ash-framework": [ |
| 66 | + # The description tells people how to use this skill. |
| 67 | + description: \""" |
| 68 | + Use this skill working with Ash Framework or any of its extensions. |
| 69 | + Always consult this when making any domain changes, features or fixes. |
| 70 | + \""", |
| 71 | + # Include all Ash dependencies |
| 72 | + usage_rules: [:ash, ~r/^ash_/] |
| 73 | + ], |
| 74 | + "phoenix-framework": [ |
| 75 | + description: \""" |
| 76 | + Use this skill working with Phoenix Framework. |
| 77 | + Consult this when working with the web layer, controllers, views, liveviews etc. |
| 78 | + \""", |
| 79 | + # Include all Phoenix dependencies |
| 80 | + usage_rules: [:phoenix, ~r/^phoenix_/] |
| 81 | + ] |
| 82 | + ] |
| 83 | + ] |
| 60 84 | ] |
| 61 85 | end |
| 62 86 | ``` |
| @@ -98,21 +122,17 @@ defp usage_rules do | |
| 98 122 | deps: [:ash, :req], |
| 99 123 | # Supports regex for matching multiple deps: |
| 100 124 | # deps: [~r/^ash_/], |
| 125 | + # Use {:dep, :reference} to build as reference files instead of inlining: |
| 126 | + # deps: [:ash, {:req, :reference}, {~r/^ash_/, :reference}], |
| 101 127 | |
| 102 128 | # Compose custom skills from multiple packages |
| 103 129 | build: [ |
| 104 | - "ash-expert": [ |
| 130 | + "ash-framework": [ |
| 105 131 | description: "Expert on the Ash Framework ecosystem.", |
| 106 | - usage_rules: [:ash, :ash_postgres, :ash_phoenix] |
| 132 | + # Use {:dep, :reference} or {~r/.../, :reference} for reference files |
| 133 | + usage_rules: [:ash, {:ash_postgres, :reference}, {~r/^ash_phoenix/, :reference}] |
| 107 134 | ] |
| 108 135 | ] |
| 109 | - # build also supports regex in usage_rules: |
| 110 | - # build: [ |
| 111 | - # "ash-expert": [ |
| 112 | - # description: "Expert on Ash.", |
| 113 | - # usage_rules: [:ash, ~r/^ash_/] |
| 114 | - # ] |
| 115 | - # ] |
| 116 136 | ] |
| 117 137 | ] |
| 118 138 | end |
| @@ -144,7 +164,7 @@ Each entry in the `usage_rules` list can be: | |
| 144 164 | | Option | Type | Description | |
| 145 165 | |--------|------|-------------| |
| 146 166 | | `location` | `string` | Output directory for skills (default: `".claude/skills"`) | |
| 147 | - | `deps` | `list` | Auto-build a `use-<pkg>` skill per listed dependency. Supports atoms and regexes | |
| 167 | + | `deps` | `list` | Auto-build a `use-<pkg>` skill per listed dependency. Supports atoms, regexes, and `{spec, :reference}` tuples | |
| 148 168 | | `build` | `keyword` | Define custom composed skills from multiple packages' usage rules | |
| 149 169 | |
| 150 170 | ## Usage Rules |
| @@ -239,6 +259,14 @@ end | |
| 239 259 | |
| 240 260 | This generates `.claude/skills/use-ash/SKILL.md` and `.claude/skills/use-req/SKILL.md`, each containing the package's usage rules, available mix tasks, doc search commands, and sub-rule references. |
| 241 261 | |
| 262 | + You can mark deps as `:reference` to have their main rules written as a separate reference file instead of being inlined into the skill body: |
| 263 | + |
| 264 | + ```elixir |
| 265 | + skills: [ |
| 266 | + deps: [{:ash, :reference}, {~r/^ash_/, :reference}] |
| 267 | + ] |
| 268 | + ``` |
| 269 | + |
| 242 270 | ### Compose custom skills |
| 243 271 | |
| 244 272 | The `build` option lets you compose a single skill from multiple packages: |
| @@ -246,7 +274,7 @@ The `build` option lets you compose a single skill from multiple packages: | |
| 246 274 | ```elixir |
| 247 275 | skills: [ |
| 248 276 | build: [ |
| 249 | - "ash-expert": [ |
| 277 | + "ash-framework": [ |
| 250 278 | description: "Expert on the Ash Framework ecosystem.", |
| 251 279 | usage_rules: [:ash, :ash_postgres, :ash_phoenix, :ash_json_api] |
| 252 280 | ] |
| @@ -254,12 +282,12 @@ skills: [ | |
| 254 282 | ] |
| 255 283 | ``` |
| 256 284 | |
| 257 | - This generates a single `.claude/skills/ash-expert/SKILL.md` that combines usage rules from all listed packages. Regex is also supported: |
| 285 | + This generates a single `.claude/skills/ash-framework/SKILL.md` that combines usage rules from all listed packages. Regex is also supported: |
| 258 286 | |
| 259 287 | ```elixir |
| 260 288 | skills: [ |
| 261 289 | build: [ |
| 262 | - "ash-expert": [ |
| 290 | + "ash-framework": [ |
| 263 291 | description: "Expert on Ash.", |
| 264 292 | usage_rules: [:ash, ~r/^ash_/] |
| 265 293 | ] |
| @@ -267,6 +295,24 @@ skills: [ | |
| 267 295 | ] |
| 268 296 | ``` |
| 269 297 | |
| 298 | + ### Reference mode |
| 299 | + |
| 300 | + By default, each package's main `usage-rules.md` is inlined directly into the skill body. For skills that combine many packages, this can make the SKILL.md quite large. You can use `{:dep, :reference}` or `{~r/.../, :reference}` to have packages built as separate reference files instead: |
| 301 | + |
| 302 | + ```elixir |
| 303 | + skills: [ |
| 304 | + build: [ |
| 305 | + "ash-framework": [ |
| 306 | + description: "Expert on the Ash Framework ecosystem.", |
| 307 | + # Ash core rules are inlined, extensions are reference files |
| 308 | + usage_rules: [:ash, {~r/^ash_/, :reference}] |
| 309 | + ] |
| 310 | + ] |
| 311 | + ] |
| 312 | + ``` |
| 313 | + |
| 314 | + This inlines `:ash`'s rules into the skill body and writes each matching extension's rules to `references/<package>.md`, linked from the "Additional References" section — the same way sub-rules are handled. This works in both `deps` and `build` configs, and with both atoms and regexes. |
| 315 | + |
| 270 316 | ### Stale skill cleanup |
| 271 317 | |
| 272 318 | Skills generated by UsageRules include a `managed-by: usage-rules` marker in their YAML frontmatter. When a skill is removed from your config and you re-run `mix usage_rules.sync`, the stale skill files are automatically cleaned up. If you've added custom content to a managed skill, only the managed section is removed — your custom content is preserved. |
| @@ -7,7 +7,7 @@ | |
| 7 7 | {<<"GitHub">>,<<"https://github.com/ash-project/usage_rules">>}, |
| 8 8 | {<<"Website">>,<<"https://ash-hq.org">>}]}. |
| 9 9 | {<<"name">>,<<"usage_rules">>}. |
| 10 | - {<<"version">>,<<"1.0.0-rc.1">>}. |
| 10 | + {<<"version">>,<<"1.0.0-rc.2">>}. |
| 11 11 | {<<"description">>, |
| 12 12 | <<"A config-driven dev tool for Elixir projects to manage AGENTS.md files and agent skills from dependencies">>}. |
| 13 13 | {<<"elixir">>,<<"~> 1.18">>}. |
| @@ -52,7 +52,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 52 52 | |> Igniter.add_notice(""" |
| 53 53 | UsageRules: Manage your AGENTS.md file and agent skills from dependencies. |
| 54 54 | |
| 55 | - Add configuration to your mix.exs project/0: |
| 55 | + Add configuration to your mix.exs project/0.: |
| 56 56 | |
| 57 57 | def project do |
| 58 58 | [ |
| @@ -61,9 +61,34 @@ if Code.ensure_loaded?(Igniter) do | |
| 61 61 | end |
| 62 62 | |
| 63 63 | defp usage_rules do |
| 64 | + # Example for those using claude. |
| 64 65 | [ |
| 65 | - file: "AGENTS.md", |
| 66 | - usage_rules: :all |
| 66 | + file: "CLAUDE.md", |
| 67 | + # rules to include directly in CLAUDE.md |
| 68 | + usage_rules: ["usage_rules:all"], |
| 69 | + skills: [ |
| 70 | + location: ".claude/skills", |
| 71 | + # build skills that combine multiple usage rules |
| 72 | + build: [ |
| 73 | + "ash-framework": [ |
| 74 | + # The description tells people how to use this skill. |
| 75 | + description: \""" |
| 76 | + Use this skill working with Ash Framework or any of its extensions. |
| 77 | + Always consult this when making any domain changes, features or fixes. |
| 78 | + \""", |
| 79 | + # Include all Ash dependencies |
| 80 | + usage_rules: [:ash, ~r/^ash_/] |
| 81 | + ], |
| 82 | + "phoenix-framework": [ |
| 83 | + description: \""" |
| 84 | + Use this skill working with Phoenix Framework. |
| 85 | + Consult this when working with the web layer, controllers, views, liveviews etc. |
| 86 | + \""", |
| 87 | + # Include all Phoenix dependencies |
| 88 | + usage_rules: [:phoenix, ~r/^phoenix_/] |
| 89 | + ] |
| 90 | + ] |
| 91 | + ] |
| 67 92 | ] |
| 68 93 | end |
| @@ -29,33 +29,7 @@ defmodule Mix.Tasks.UsageRules.Sync.Docs do | |
| 29 29 | Add to your `mix.exs` project config: |
| 30 30 | |
| 31 31 | ```elixir |
| 32 | - def project do |
| 33 | - [ |
| 34 | - usage_rules: usage_rules() |
| 35 | - ] |
| 36 | - end |
| 37 | - |
| 38 | - defp usage_rules do |
| 39 | - [ |
| 40 | - file: "AGENTS.md", |
| 41 | - usage_rules: [ |
| 42 | - :ash, # main usage-rules.md (inlined) |
| 43 | - "phoenix:ecto", # specific sub-rule (inlined) |
| 44 | - {:req, link: :at}, # linked with @-style |
| 45 | - ], |
| 46 | - # or: usage_rules: :all |
| 47 | - skills: [ |
| 48 | - location: ".claude/skills", # where to output skills |
| 49 | - deps: [:ash, :req], # auto-build a "use-<pkg>" skill per dep |
| 50 | - build: [ # compose custom skills from multiple packages |
| 51 | - "ash-expert": [ |
| 52 | - description: "Expert on the Ash Framework ecosystem.", |
| 53 | - usage_rules: [:ash, :ash_postgres, :ash_json_api] |
| 54 | - ] |
| 55 | - ] |
| 56 | - ] |
| 57 | - ] |
| 58 | - end |
| 32 | + #{code_sample()} |
| 59 33 | ``` |
| 60 34 | |
| 61 35 | Then run: |
| @@ -67,6 +41,51 @@ defmodule Mix.Tasks.UsageRules.Sync.Docs do | |
| 67 41 | from config are automatically removed on each sync. |
| 68 42 | """ |
| 69 43 | end |
| 44 | + |
| 45 | + def code_sample(spaces \\ 0) do |
| 46 | + """ |
| 47 | + def project do |
| 48 | + [ |
| 49 | + ... |
| 50 | + usage_rules: usage_rules() |
| 51 | + ] |
| 52 | + end |
| 53 | + |
| 54 | + defp usage_rules do |
| 55 | + # Example for those using claude. |
| 56 | + [ |
| 57 | + file: "CLAUDE.md", |
| 58 | + # rules to include directly in CLAUDE.md |
| 59 | + usage_rules: ["usage_rules:all"], |
| 60 | + skills: [ |
| 61 | + location: ".claude/skills", |
| 62 | + # build skills that combine multiple usage rules |
| 63 | + build: [ |
| 64 | + "ash-framework": [ |
| 65 | + # The description tells people how to use this skill. |
| 66 | + description: \""" |
| 67 | + Use this skill working with Ash Framework or any of its extensions. |
| 68 | + Always consult this when making any domain changes, features or fixes. |
| 69 | + \""", |
| 70 | + # Include all Ash dependencies |
| 71 | + usage_rules: [:ash, ~r/^ash_/] |
| 72 | + ], |
| 73 | + "phoenix-framework": [ |
| 74 | + description: \""" |
| 75 | + Use this skill working with Phoenix Framework. |
| 76 | + Consult this when working with the web layer, controllers, views, liveviews etc. |
| 77 | + \""", |
| 78 | + # Include all Phoenix dependencies |
| 79 | + usage_rules: [:phoenix, ~r/^phoenix_/] |
| 80 | + ] |
| 81 | + ] |
| 82 | + ] |
| 83 | + ] |
| 84 | + end |
| 85 | + """ |
| 86 | + |> String.split("\n") |
| 87 | + |> Enum.map_join("\n", &String.pad_leading(&1, String.length(&1) + spaces)) |
| 88 | + end |
| 70 89 | end |
| 71 90 | |
| 72 91 | if Code.ensure_loaded?(Igniter) do |
| @@ -87,18 +106,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 87 106 | |
| 88 107 | Configuration is now done in your `mix.exs` project config: |
| 89 108 | |
| 90 | - def project do |
| 91 | - [ |
| 92 | - usage_rules: usage_rules() |
| 93 | - ] |
| 94 | - end |
| 95 | - |
| 96 | - defp usage_rules do |
| 97 | - [ |
| 98 | - file: "AGENTS.md", |
| 99 | - usage_rules: :all |
| 100 | - ] |
| 101 | - end |
| 109 | + #{__MODULE__.Docs.code_sample(4)} |
| 102 110 | |
| 103 111 | Then simply run: mix usage_rules.sync |
| 104 112 | |
| @@ -170,18 +178,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 170 178 | """ |
| 171 179 | No usage_rules config found. Add to your mix.exs project config: |
| 172 180 | |
| 173 | - def project do |
| 174 | - [ |
| 175 | - usage_rules: usage_rules() |
| 176 | - ] |
| 177 | - end |
| 178 | - |
| 179 | - defp usage_rules do |
| 180 | - [ |
| 181 | - file: "AGENTS.md", |
| 182 | - usage_rules: [:ash, :phoenix] |
| 183 | - ] |
| 184 | - end |
| 181 | + #{__MODULE__.Docs.code_sample(4)} |
| 185 182 | """} |
| 186 183 | |
| 187 184 | (link_error = validate_link_options(config[:usage_rules])) != nil -> |
| @@ -255,11 +252,12 @@ if Code.ensure_loaded?(Igniter) do | |
| 255 252 | package_build_specs = |
| 256 253 | (skills_config[:deps] || []) |
| 257 254 | |> expand_dep_specs(all_deps) |
| 258 | - |> Enum.filter(fn {_name, path} -> |
| 255 | + |> Enum.filter(fn {_name, path, _mode} -> |
| 259 256 | Igniter.exists?(igniter, Path.join(path, "usage-rules.md")) |
| 260 257 | end) |
| 261 | - |> Enum.map(fn {pkg_name, _path} -> |
| 262 | - {:"use-#{pkg_name}", [usage_rules: [pkg_name]]} |
| 258 | + |> Enum.map(fn {pkg_name, _path, mode} -> |
| 259 | + spec = if mode == :reference, do: {pkg_name, :reference}, else: pkg_name |
| 260 | + {:"use-#{pkg_name}", [usage_rules: [spec]]} |
| 263 261 | end) |
| 264 262 | |
| 265 263 | # Merge explicit build specs on top of package-derived ones |
| @@ -735,8 +733,29 @@ if Code.ensure_loaded?(Igniter) do | |
| 735 733 | end |
| 736 734 | ) |
| 737 735 | |
| 738 | - # Reference files for sub-rules from all packages |
| 739 | - Enum.reduce(resolved_packages, igniter, fn {_pkg_name, package_path}, acc -> |
| 736 | + # Reference files for sub-rules and reference-mode main rules from all packages |
| 737 | + Enum.reduce(resolved_packages, igniter, fn {pkg_name, package_path, mode}, acc -> |
| 738 | + # Create reference file for main usage-rules.md if this package is in reference mode |
| 739 | + acc = |
| 740 | + if mode == :reference do |
| 741 | + main_path = Path.join(package_path, "usage-rules.md") |
| 742 | + content = read_dep_content(acc, main_path) |
| 743 | + ref_path = Path.join([skill_dir, "references", "#{pkg_name}.md"]) |
| 744 | + |
| 745 | + if content != "" do |
| 746 | + Igniter.create_or_update_file( |
| 747 | + acc, |
| 748 | + ref_path, |
| 749 | + content, |
| 750 | + fn source -> Rewrite.Source.update(source, :content, content) end |
| 751 | + ) |
| 752 | + else |
| 753 | + acc |
| 754 | + end |
| 755 | + else |
| 756 | + acc |
| 757 | + end |
| 758 | + |
| 740 759 | sub_rules = find_available_sub_rules(acc, package_path) |
| 741 760 | |
| 742 761 | Enum.reduce(sub_rules, acc, fn sub_rule, inner_acc -> |
| @@ -770,7 +789,8 @@ if Code.ensure_loaded?(Igniter) do | |
| 770 789 | |
| 771 790 | body = build_skill_body(igniter, skill_name, resolved_packages) |
| 772 791 | |
| 773 | - frontmatter <> "\n\n" <> |
| 792 | + frontmatter <> |
| 793 | + "\n\n" <> |
| 774 794 | "<!-- usage-rules-skill-start -->\n" <> |
| 775 795 | body <> |
| 776 796 | "\n<!-- usage-rules-skill-end -->" |
| @@ -797,25 +817,38 @@ if Code.ensure_loaded?(Igniter) do | |
| 797 817 | |
| 798 818 | # Sub-rules as references (at the top for discoverability) |
| 799 819 | all_sub_rules = |
| 800 | - Enum.flat_map(resolved_packages, fn {_pkg_name, package_path} -> |
| 820 | + Enum.flat_map(resolved_packages, fn {_pkg_name, package_path, _mode} -> |
| 801 821 | find_available_sub_rules(igniter, package_path) |
| 802 822 | end) |
| 803 823 | |
| 804 | - sections = |
| 805 | - if Enum.any?(all_sub_rules) do |
| 806 | - ref_lines = |
| 807 | - Enum.map_join(all_sub_rules, "\n", fn sub_rule -> |
| 824 | + # Main rules from reference-mode packages |
| 825 | + reference_main_rules = |
| 826 | + resolved_packages |
| 827 | + |> Enum.filter(fn {_pkg_name, _path, mode} -> mode == :reference end) |
| 828 | + |> Enum.map(fn {pkg_name, _path, _mode} -> pkg_name end) |
| 829 | + |
| 830 | + all_references = |
| 831 | + Enum.map(all_sub_rules, fn sub_rule -> |
| 808 832 | "- [#{sub_rule}](references/#{sub_rule}.md)" |
| 833 | + end) ++ |
| 834 | + Enum.map(reference_main_rules, fn pkg_name -> |
| 835 | + "- [#{pkg_name}](references/#{pkg_name}.md)" |
| 809 836 | end) |
| 810 837 | |
| 838 | + sections = |
| 839 | + if Enum.any?(all_references) do |
| 840 | + ref_lines = Enum.join(all_references, "\n") |
| 811 841 | sections ++ ["## Additional References\n\n#{ref_lines}"] |
| 812 842 | else |
| 813 843 | sections |
| 814 844 | end |
| 815 845 | |
| 816 | - # Usage rules content from all packages |
| 846 | + # Usage rules content from inline packages only |
| 817 847 | sections = |
| 818 | - Enum.reduce(resolved_packages, sections, fn {pkg_name, package_path}, acc -> |
| 848 | + Enum.reduce(resolved_packages, sections, fn {pkg_name, package_path, mode}, acc -> |
| 849 | + if mode == :reference do |
| 850 | + acc |
| 851 | + else |
| 819 852 | main_path = Path.join(package_path, "usage-rules.md") |
| 820 853 | content = read_dep_content(igniter, main_path) |
| 821 854 | |
| @@ -824,6 +857,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 824 857 | else |
| 825 858 | acc |
| 826 859 | end |
| 860 | + end |
| 827 861 | end) |
| 828 862 | |
| 829 863 | # Search docs for all packages |
| @@ -846,7 +880,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 846 880 | |
| 847 881 | # Mix tasks from all packages (at the bottom) |
| 848 882 | all_mix_tasks = |
| 849 | - Enum.flat_map(resolved_packages, fn {pkg_name, _path} -> |
| 883 | + Enum.flat_map(resolved_packages, fn {pkg_name, _path, _mode} -> |
| 850 884 | discover_mix_tasks(pkg_name) |
| 851 885 | |> Enum.map(fn {task, doc} -> {pkg_name, task, doc} end) |
| 852 886 | end) |
| @@ -908,15 +942,28 @@ if Code.ensure_loaded?(Igniter) do | |
| 908 942 | |
| 909 943 | defp expand_dep_specs(specs, all_deps) do |
| 910 944 | Enum.flat_map(specs, fn |
| 945 | + {%Regex{} = regex, :reference} -> |
| 946 | + Enum.filter(all_deps, fn {name, _path} -> |
| 947 | + Regex.match?(regex, to_string(name)) |
| 948 | + end) |
| 949 | + |> Enum.map(fn {name, path} -> {name, path, :reference} end) |
| 950 | + |
| 951 | + {pkg_name, :reference} when is_atom(pkg_name) -> |
| 952 | + case Enum.find(all_deps, fn {name, _path} -> name == pkg_name end) do |
| 953 | + nil -> [] |
| 954 | + {name, path} -> [{name, path, :reference}] |
| 955 | + end |
| 956 | + |
| 911 957 | %Regex{} = regex -> |
| 912 958 | Enum.filter(all_deps, fn {name, _path} -> |
| 913 959 | Regex.match?(regex, to_string(name)) |
| 914 960 | end) |
| 961 | + |> Enum.map(fn {name, path} -> {name, path, :inline} end) |
| 915 962 | |
| 916 963 | pkg_name when is_atom(pkg_name) -> |
| 917 964 | case Enum.find(all_deps, fn {name, _path} -> name == pkg_name end) do |
| 918 965 | nil -> [] |
| 919 | - dep -> [dep] |
| 966 | + {name, path} -> [{name, path, :inline}] |
| 920 967 | end |
| 921 968 | end) |
| 922 969 | |> Enum.uniq_by(&elem(&1, 0)) |
Loading more files…