Current section

42 Versions

Jump to

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…