Current section

42 Versions

Jump to

Compare versions

7 files changed
+217 additions
-31 deletions
  @@ -5,6 +5,19 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
5 5
6 6 <!-- changelog -->
7 7
8 + ## v0.1.8 (2025-06-24)
9 +
10 +
11 +
12 +
13 + ### Improvements:
14 +
15 + * remove a confusing usage rule on list purposes by Zach Daniel
16 +
17 + * add a usage rule about indexing lists/enumerables by Zach Daniel
18 +
19 + * add builtin usage rules by Zach Daniel
20 +
8 21 ## v0.1.7 (2025-06-23)
  @@ -46,7 +46,9 @@ This will create a folder called `rules`, with a file per package that has a `us
46 46 to those from you rules file.
47 47
48 48 ```sh
49 - mix usage_rules.sync CLAUDE.md --all --link-to-folder docs
49 + mix usage_rules.sync CLAUDE.md --all \
50 + --link-to-folder docs \
51 + --builtins elixir,otp
50 52 ```
51 53
52 54 #### Using deps folder
  @@ -6,16 +6,17 @@
6 6 {<<"GitHub">>,<<"https://github.com/ash-project/usage_rules">>},
7 7 {<<"Website">>,<<"https://ash-hq.org">>}]}.
8 8 {<<"name">>,<<"usage_rules">>}.
9 - {<<"version">>,<<"0.1.7">>}.
9 + {<<"version">>,<<"0.1.8">>}.
10 10 {<<"description">>,
11 11 <<"A dev tool for Elixir projects to gather LLM usage rules from dependencies">>}.
12 12 {<<"elixir">>,<<"~> 1.18">>}.
13 13 {<<"files">>,
14 14 [<<"lib">>,<<"lib/mix">>,<<"lib/mix/tasks">>,
15 15 <<"lib/mix/tasks/usage_rules.install.ex">>,
16 - <<"lib/mix/tasks/usage_rules.sync.ex">>,<<"lib/usage_rules.ex">>,
17 - <<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,
18 - <<"CHANGELOG.md">>]}.
16 + <<"lib/mix/tasks/usage_rules.sync.ex">>,<<"lib/usage_rules.ex">>,<<"priv">>,
17 + <<"priv/builtins">>,<<"priv/builtins/elixir.md">>,
18 + <<"priv/builtins/otp.md">>,<<".formatter.exs">>,<<"mix.exs">>,
19 + <<"README.md">>,<<"LICENSE">>,<<"CHANGELOG.md">>]}.
19 20 {<<"app">>,<<"usage_rules">>}.
20 21 {<<"licenses">>,[<<"MIT">>]}.
21 22 {<<"requirements">>,
  @@ -23,6 +23,7 @@ defmodule Mix.Tasks.UsageRules.Sync.Docs do
23 23 * `--remove` - Remove specified packages from the target file instead of adding them
24 24 * `--link-to-folder <folder>` - Save usage rules for each package in separate files within the specified folder and create links to them
25 25 * `--link-style <style>` - Style of links to create when using --link-to-folder (markdown|at). Defaults to 'markdown'
26 + * `--builtins <builtins>` - Include built-in usage rules (comma-separated: elixir,otp)
26 27
27 28 ## Examples
28 29
  @@ -80,6 +81,11 @@ defmodule Mix.Tasks.UsageRules.Sync.Docs do
80 81 ```sh
81 82 mix usage_rules.sync CLAUDE.md ash phoenix --remove --link-to-folder rules
82 83 ```
84 +
85 + Include built-in Elixir and OTP usage rules:
86 + ```sh
87 + mix usage_rules.sync CLAUDE.md --all --link-to-folder deps --builtins elixir,otp
88 + ```
83 89 """
84 90 end
85 91 end
  @@ -108,7 +114,8 @@ if Code.ensure_loaded?(Igniter) do
108 114 list: :boolean,
109 115 remove: :boolean,
110 116 link_to_folder: :string,
111 - link_style: :string
117 + link_style: :string,
118 + builtins: :string
112 119 ]
113 120 }
114 121 end
  @@ -149,8 +156,19 @@ if Code.ensure_loaded?(Igniter) do
149 156 link_to_folder = igniter.args.options[:link_to_folder]
150 157 link_style = igniter.args.options[:link_style] || "markdown"
151 158 provided_packages = igniter.args.positional.packages
159 + builtins = parse_builtins(igniter.args.options[:builtins])
152 160
153 161 cond do
162 + # If --builtins contains invalid values, add error
163 + igniter.args.options[:builtins] &&
164 + parse_invalid_builtins(igniter.args.options[:builtins]) != [] ->
165 + invalid = parse_invalid_builtins(igniter.args.options[:builtins])
166 +
167 + Igniter.add_issue(
168 + igniter,
169 + "Invalid builtins: #{Enum.join(invalid, ", ")}. Valid options are: elixir, otp"
170 + )
171 +
154 172 # If --link-style is used with invalid value, add error
155 173 link_style && link_style not in ["markdown", "at"] ->
156 174 Igniter.add_issue(igniter, "--link-style must be either 'markdown' or 'at'")
  @@ -193,7 +211,7 @@ if Code.ensure_loaded?(Igniter) do
193 211
194 212 # Handle --all option
195 213 all_option ->
196 - handle_all_option(igniter, all_deps, link_to_folder, link_style)
214 + handle_all_option(igniter, all_deps, link_to_folder, link_style, builtins)
197 215
198 216 # Handle --list option
199 217 list_option ->
  @@ -206,11 +224,48 @@ if Code.ensure_loaded?(Igniter) do
206 224 all_deps,
207 225 provided_packages,
208 226 link_to_folder,
209 - link_style
227 + link_style,
228 + builtins
210 229 )
211 230 end
212 231 end
213 232
233 + defp parse_builtins(nil), do: []
234 +
235 + defp parse_builtins(builtins_string) do
236 + builtins_string
237 + |> String.split(",")
238 + |> Enum.map(&String.trim/1)
239 + |> Enum.filter(fn builtin ->
240 + builtin in ["elixir", "otp"]
241 + end)
242 + end
243 +
244 + defp parse_invalid_builtins(nil), do: []
245 +
246 + defp parse_invalid_builtins(builtins_string) do
247 + builtins_string
248 + |> String.split(",")
249 + |> Enum.map(&String.trim/1)
250 + |> Enum.reject(fn builtin ->
251 + builtin in ["", "elixir", "otp"]
252 + end)
253 + end
254 +
255 + defp get_builtin_contents(builtins) do
256 + builtins
257 + |> Enum.map(fn builtin ->
258 + builtin_path = Path.join([:code.priv_dir(:usage_rules), "builtins", "#{builtin}.md"])
259 + content = File.read!(builtin_path)
260 +
261 + {String.to_atom(builtin),
262 + "<!-- #{builtin}-start -->\n" <>
263 + "## #{builtin} usage\n" <>
264 + content <>
265 + "\n<!-- #{builtin}-end -->"}
266 + end)
267 + end
268 +
214 269 defp get_deps_from_igniter(igniter) do
215 270 if igniter.assigns[:test_mode?] do
216 271 igniter.rewrite.sources
  @@ -265,7 +320,7 @@ if Code.ensure_loaded?(Igniter) do
265 320 """)
266 321 end
267 322
268 - defp handle_all_option(igniter, all_deps, link_to_folder, link_style) do
323 + defp handle_all_option(igniter, all_deps, link_to_folder, link_style, builtins) do
269 324 all_packages_with_rules = get_packages_with_usage_rules(igniter, all_deps)
270 325
271 326 igniter
  @@ -277,7 +332,16 @@ if Code.ensure_loaded?(Igniter) do
277 332 Igniter.add_notice(acc, "Including usage rules for: #{name}")
278 333 end)
279 334 end)
280 - |> generate_usage_rules_file(all_packages_with_rules, link_to_folder, link_style)
335 + |> maybe_add_builtin_notices(builtins)
336 + |> generate_usage_rules_file(all_packages_with_rules, link_to_folder, link_style, builtins)
337 + end
338 +
339 + defp maybe_add_builtin_notices(igniter, []), do: igniter
340 +
341 + defp maybe_add_builtin_notices(igniter, builtins) do
342 + Enum.reduce(builtins, igniter, fn builtin, acc ->
343 + Igniter.add_notice(acc, "Including built-in usage rules for: #{builtin}")
344 + end)
281 345 end
282 346
283 347 defp handle_list_option(igniter, all_deps, link_to_folder) do
  @@ -306,7 +370,8 @@ if Code.ensure_loaded?(Igniter) do
306 370 all_deps,
307 371 provided_packages,
308 372 link_to_folder,
309 - link_style
373 + link_style,
374 + builtins
310 375 ) do
311 376 packages =
312 377 all_deps
  @@ -323,7 +388,9 @@ if Code.ensure_loaded?(Igniter) do
323 388 end
324 389 end)
325 390
326 - generate_usage_rules_file(igniter, packages, link_to_folder, link_style)
391 + igniter
392 + |> maybe_add_builtin_notices(builtins)
393 + |> generate_usage_rules_file(packages, link_to_folder, link_style, builtins)
327 394 end
328 395
329 396 defp handle_remove_packages(igniter, provided_packages, link_to_folder) do
  @@ -397,15 +464,21 @@ if Code.ensure_loaded?(Igniter) do
397 464 end
398 465 end
399 466
400 - defp generate_usage_rules_file(igniter, packages, link_to_folder, link_style) do
467 + defp generate_usage_rules_file(igniter, packages, link_to_folder, link_style, builtins) do
401 468 if link_to_folder do
402 - generate_usage_rules_with_folder_links(igniter, packages, link_to_folder, link_style)
469 + generate_usage_rules_with_folder_links(
470 + igniter,
471 + packages,
472 + link_to_folder,
473 + link_style,
474 + builtins
475 + )
403 476 else
404 - generate_usage_rules_inline(igniter, packages)
477 + generate_usage_rules_inline(igniter, packages, builtins)
405 478 end
406 479 end
407 480
408 - defp generate_usage_rules_inline(igniter, packages) do
481 + defp generate_usage_rules_inline(igniter, packages, builtins) do
409 482 package_contents =
410 483 packages
411 484 |> Enum.map(fn {name, path} ->
  @@ -424,11 +497,14 @@ if Code.ensure_loaded?(Igniter) do
424 497 "\n<!-- #{name}-end -->"}
425 498 end)
426 499
427 - package_rules_content = Enum.map_join(package_contents, "\n", &elem(&1, 1))
500 + builtin_contents = get_builtin_contents(builtins)
501 +
502 + all_contents = package_contents ++ builtin_contents
503 + all_rules_content = Enum.map_join(all_contents, "\n", &elem(&1, 1))
428 504
429 505 full_contents_for_new_file =
430 506 "<!-- usage-rules-start -->\n" <>
431 - package_rules_content <>
507 + all_rules_content <>
432 508 "\n<!-- usage-rules-end -->"
433 509
434 510 Igniter.create_or_update_file(
  @@ -444,9 +520,8 @@ if Code.ensure_loaded?(Igniter) do
444 520 "\n<!-- usage-rules-end -->"
445 521 ]) do
446 522 [prelude, current_packages_contents, postlude] ->
447 - Enum.reduce(package_contents, current_packages_contents, fn {name,
448 - package_content},
449 - acc ->
523 + Enum.reduce(all_contents, current_packages_contents, fn {name, package_content},
524 + acc ->
450 525 case String.split(acc, [
451 526 "<!-- #{name}-start -->\n",
452 527 "\n<!-- #{name}-end -->"
  @@ -469,7 +544,7 @@ if Code.ensure_loaded?(Igniter) do
469 544 _ ->
470 545 current_contents <>
471 546 "\n<!-- usage-rules-start -->\n" <>
472 - package_rules_content <>
547 + all_rules_content <>
473 548 "\n<!-- usage-rules-end -->\n"
474 549 end
475 550
  @@ -482,13 +557,33 @@ if Code.ensure_loaded?(Igniter) do
482 557 igniter,
483 558 packages,
484 559 folder_name,
485 - link_style
560 + link_style,
561 + builtins
486 562 ) do
487 563 # Create individual files for each package in the folder (unless folder is "deps")
488 564 igniter =
489 565 if folder_name == "deps" do
490 566 igniter
491 567 else
568 + # Create builtin files in the target folder
569 + igniter =
570 + Enum.reduce(builtins, igniter, fn builtin, acc ->
571 + builtin_source_path =
572 + Path.join([:code.priv_dir(:usage_rules), "builtins", "#{builtin}.md"])
573 +
574 + builtin_file_path = Path.join(folder_name, "#{builtin}.md")
575 + content = File.read!(builtin_source_path)
576 +
577 + Igniter.create_or_update_file(
578 + acc,
579 + builtin_file_path,
580 + content,
581 + fn source ->
582 + Rewrite.Source.update(source, :content, content)
583 + end
584 + )
585 + end)
586 +
492 587 Enum.reduce(packages, igniter, fn {name, path}, acc ->
493 588 usage_rules_path = Path.join(path, "usage-rules.md")
494 589
  @@ -530,11 +625,37 @@ if Code.ensure_loaded?(Igniter) do
530 625 "\n<!-- #{name}-end -->"}
531 626 end)
532 627
533 - package_rules_content = Enum.map_join(package_contents, "\n", &elem(&1, 1))
628 + builtin_contents =
629 + builtins
630 + |> Enum.map(fn builtin ->
631 + link_content =
632 + case {link_style, folder_name} do
633 + {"at", "deps"} ->
634 + "@deps/usage_rules/priv/builtins/#{builtin}.md"
635 +
636 + {"at", _} ->
637 + "@#{folder_name}/#{builtin}.md"
638 +
639 + {_, "deps"} ->
640 + "[#{builtin} usage rules](deps/usage_rules/priv/builtins/#{builtin}.md)"
641 +
642 + _ ->
643 + "[#{builtin} usage rules](#{folder_name}/#{builtin}.md)"
644 + end
645 +
646 + {String.to_atom(builtin),
647 + "<!-- #{builtin}-start -->\n" <>
648 + "## #{builtin} usage\n" <>
649 + link_content <>
650 + "\n<!-- #{builtin}-end -->"}
651 + end)
652 +
653 + all_contents = package_contents ++ builtin_contents
654 + all_rules_content = Enum.map_join(all_contents, "\n", &elem(&1, 1))
534 655
535 656 full_contents_for_new_file =
536 657 "<!-- usage-rules-start -->\n" <>
537 - package_rules_content <>
658 + all_rules_content <>
538 659 "\n<!-- usage-rules-end -->"
539 660
540 661 Igniter.create_or_update_file(
  @@ -550,9 +671,8 @@ if Code.ensure_loaded?(Igniter) do
550 671 "\n<!-- usage-rules-end -->"
551 672 ]) do
552 673 [prelude, current_packages_contents, postlude] ->
553 - Enum.reduce(package_contents, current_packages_contents, fn {name,
554 - package_content},
555 - acc ->
674 + Enum.reduce(all_contents, current_packages_contents, fn {name, package_content},
675 + acc ->
556 676 case String.split(acc, [
557 677 "<!-- #{name}-start -->\n",
558 678 "\n<!-- #{name}-end -->"
  @@ -575,7 +695,7 @@ if Code.ensure_loaded?(Igniter) do
575 695 _ ->
576 696 current_contents <>
577 697 "\n<!-- usage-rules-start -->\n" <>
578 - package_rules_content <>
698 + all_rules_content <>
579 699 "\n<!-- usage-rules-end -->\n"
580 700 end
  @@ -1,7 +1,7 @@
1 1 defmodule UsageRules.MixProject do
2 2 use Mix.Project
3 3
4 - @version "0.1.7"
4 + @version "0.1.8"
5 5 @description """
6 6 A dev tool for Elixir projects to gather LLM usage rules from dependencies
7 7 """
  @@ -31,7 +31,7 @@ defmodule UsageRules.MixProject do
31 31 name: :usage_rules,
32 32 licenses: ["MIT"],
33 33 maintainers: "Zach Daniel",
34 - files: ~w(lib .formatter.exs mix.exs README* LICENSE* CHANGELOG*),
34 + files: ~w(lib priv .formatter.exs mix.exs README* LICENSE* CHANGELOG*),
35 35 links: %{
36 36 "GitHub" => @source_url,
37 37 "Discord" => "https://discord.gg/HTHRaaVPUc",
Loading more files…