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
5
files changed
+173
additions
-54
deletions
| @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline | |
| 5 5 | |
| 6 6 | <!-- changelog --> |
| 7 7 | |
| 8 | + ## v0.1.11 (2025-06-25) |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | + ### Improvements: |
| 14 | + |
| 15 | + * add descriptions to usage rules by Zach Daniel |
| 16 | + |
| 17 | + * make builtin usage rules always show inline by Zach Daniel |
| 18 | + |
| 8 19 | ## v0.1.10 (2025-06-25) |
| @@ -5,16 +5,12 @@ | |
| 5 5 | ## Quickstart |
| 6 6 | |
| 7 7 | ```sh |
| 8 | - # swap AGENTS.md out for a different file |
| 9 | - # if you need, like `CLAUDE.md` |
| 10 | - |
| 11 | - # sync core usage rules directly into your |
| 12 | - # agents file |
| 13 | - mix usage_rules.sync AGENTS.md --builtins elixir,otp |
| 8 | + # swap AGENTS.md out for any file you like, e.g `CLAUDE.md` |
| 14 9 | |
| 15 10 | # sync projects as links to their usage rules |
| 16 11 | # to save tokens. Agent can view them on demand |
| 17 12 | mix usage_rules.sync AGENTS.md --all \ |
| 13 | + --builtins elixir,otp \ |
| 18 14 | --link-to-folder deps |
| 19 15 | ``` |
| @@ -6,7 +6,7 @@ | |
| 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.10">>}. |
| 9 | + {<<"version">>,<<"0.1.11">>}. |
| 10 10 | {<<"description">>, |
| 11 11 | <<"A dev tool for Elixir projects to gather LLM usage rules from dependencies">>}. |
| 12 12 | {<<"elixir">>,<<"~> 1.18">>}. |
| @@ -24,6 +24,7 @@ defmodule Mix.Tasks.UsageRules.Sync.Docs do | |
| 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 26 | * `--builtins <builtins>` - Include built-in usage rules (comma-separated: elixir,otp) |
| 27 | + * `--builtins-link` - Make built-in usage rules honor the --link-to-folder option (default: false, builtins are inlined) |
| 27 28 | |
| 28 29 | ## Examples |
| 29 30 | |
| @@ -86,6 +87,10 @@ defmodule Mix.Tasks.UsageRules.Sync.Docs do | |
| 86 87 | ```sh |
| 87 88 | mix usage_rules.sync CLAUDE.md --all --link-to-folder deps --builtins elixir,otp |
| 88 89 | ``` |
| 90 | + Include built-in usage rules with links to folder: |
| 91 | + ```sh |
| 92 | + mix usage_rules.sync CLAUDE.md --all --link-to-folder docs --builtins elixir,otp --builtins-link |
| 93 | + ``` |
| 89 94 | """ |
| 90 95 | end |
| 91 96 | end |
| @@ -115,7 +120,8 @@ if Code.ensure_loaded?(Igniter) do | |
| 115 120 | remove: :boolean, |
| 116 121 | link_to_folder: :string, |
| 117 122 | link_style: :string, |
| 118 | - builtins: :string |
| 123 | + builtins: :string, |
| 124 | + builtins_link: :boolean |
| 119 125 | ] |
| 120 126 | } |
| 121 127 | end |
| @@ -157,6 +163,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 157 163 | link_style = igniter.args.options[:link_style] || "markdown" |
| 158 164 | provided_packages = igniter.args.positional.packages |
| 159 165 | builtins = parse_builtins(igniter.args.options[:builtins]) |
| 166 | + builtins_link = igniter.args.options[:builtins_link] |
| 160 167 | |
| 161 168 | cond do |
| 162 169 | # If --builtins contains invalid values, add error |
| @@ -211,7 +218,14 @@ if Code.ensure_loaded?(Igniter) do | |
| 211 218 | |
| 212 219 | # Handle --all option |
| 213 220 | all_option -> |
| 214 | - handle_all_option(igniter, all_deps, link_to_folder, link_style, builtins) |
| 221 | + handle_all_option( |
| 222 | + igniter, |
| 223 | + all_deps, |
| 224 | + link_to_folder, |
| 225 | + link_style, |
| 226 | + builtins, |
| 227 | + builtins_link |
| 228 | + ) |
| 215 229 | |
| 216 230 | # Handle --list option |
| 217 231 | list_option -> |
| @@ -225,7 +239,8 @@ if Code.ensure_loaded?(Igniter) do | |
| 225 239 | provided_packages, |
| 226 240 | link_to_folder, |
| 227 241 | link_style, |
| 228 | - builtins |
| 242 | + builtins, |
| 243 | + builtins_link |
| 229 244 | ) |
| 230 245 | end |
| 231 246 | end |
| @@ -258,14 +273,43 @@ if Code.ensure_loaded?(Igniter) do | |
| 258 273 | builtin_path = Path.join([:code.priv_dir(:usage_rules), "builtins", "#{builtin}.md"]) |
| 259 274 | content = File.read!(builtin_path) |
| 260 275 | |
| 276 | + description = |
| 277 | + case builtin do |
| 278 | + "elixir" -> "Core Elixir language features and standard library" |
| 279 | + "otp" -> "OTP (Open Telecom Platform) behaviors and patterns" |
| 280 | + _ -> "" |
| 281 | + end |
| 282 | + |
| 283 | + description_part = if description == "", do: "", else: "_#{description}_\n\n" |
| 284 | + |
| 261 285 | {String.to_atom(builtin), |
| 262 286 | "<!-- #{builtin}-start -->\n" <> |
| 263 287 | "## #{builtin} usage\n" <> |
| 288 | + description_part <> |
| 264 289 | content <> |
| 265 290 | "\n<!-- #{builtin}-end -->"} |
| 266 291 | end) |
| 267 292 | end |
| 268 293 | |
| 294 | + defp usage_rules_header do |
| 295 | + """ |
| 296 | + <!-- usage-rules-header --> |
| 297 | + # Usage Rules |
| 298 | + |
| 299 | + **IMPORTANT**: Consult these usage rules early and often when working with the packages listed below. |
| 300 | + Before attempting to use any of these packages or to discover if you should use them, review their |
| 301 | + usage rules to understand the correct patterns, conventions, and best practices. |
| 302 | + <!-- usage-rules-header-end --> |
| 303 | + """ |
| 304 | + end |
| 305 | + |
| 306 | + defp get_package_description(name) do |
| 307 | + case Application.spec(name, :description) do |
| 308 | + nil -> "" |
| 309 | + desc -> to_string(desc) |
| 310 | + end |
| 311 | + end |
| 312 | + |
| 269 313 | defp get_deps_from_igniter(igniter) do |
| 270 314 | if igniter.assigns[:test_mode?] do |
| 271 315 | igniter.rewrite.sources |
| @@ -320,7 +364,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 320 364 | """) |
| 321 365 | end |
| 322 366 | |
| 323 | - defp handle_all_option(igniter, all_deps, link_to_folder, link_style, builtins) do |
| 367 | + defp handle_all_option(igniter, all_deps, link_to_folder, link_style, builtins, builtins_link) do |
| 324 368 | all_packages_with_rules = get_packages_with_usage_rules(igniter, all_deps) |
| 325 369 | |
| 326 370 | igniter |
| @@ -333,7 +377,13 @@ if Code.ensure_loaded?(Igniter) do | |
| 333 377 | end) |
| 334 378 | end) |
| 335 379 | |> maybe_add_builtin_notices(builtins) |
| 336 | - |> generate_usage_rules_file(all_packages_with_rules, link_to_folder, link_style, builtins) |
| 380 | + |> generate_usage_rules_file( |
| 381 | + all_packages_with_rules, |
| 382 | + link_to_folder, |
| 383 | + link_style, |
| 384 | + builtins, |
| 385 | + builtins_link |
| 386 | + ) |
| 337 387 | end |
| 338 388 | |
| 339 389 | defp maybe_add_builtin_notices(igniter, []), do: igniter |
| @@ -371,7 +421,8 @@ if Code.ensure_loaded?(Igniter) do | |
| 371 421 | provided_packages, |
| 372 422 | link_to_folder, |
| 373 423 | link_style, |
| 374 | - builtins |
| 424 | + builtins, |
| 425 | + builtins_link |
| 375 426 | ) do |
| 376 427 | packages = |
| 377 428 | all_deps |
| @@ -390,7 +441,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 390 441 | |
| 391 442 | igniter |
| 392 443 | |> maybe_add_builtin_notices(builtins) |
| 393 | - |> generate_usage_rules_file(packages, link_to_folder, link_style, builtins) |
| 444 | + |> generate_usage_rules_file(packages, link_to_folder, link_style, builtins, builtins_link) |
| 394 445 | end |
| 395 446 | |
| 396 447 | defp handle_remove_packages(igniter, provided_packages, link_to_folder) do |
| @@ -464,14 +515,22 @@ if Code.ensure_loaded?(Igniter) do | |
| 464 515 | end |
| 465 516 | end |
| 466 517 | |
| 467 | - defp generate_usage_rules_file(igniter, packages, link_to_folder, link_style, builtins) do |
| 518 | + defp generate_usage_rules_file( |
| 519 | + igniter, |
| 520 | + packages, |
| 521 | + link_to_folder, |
| 522 | + link_style, |
| 523 | + builtins, |
| 524 | + builtins_link |
| 525 | + ) do |
| 468 526 | if link_to_folder do |
| 469 527 | generate_usage_rules_with_folder_links( |
| 470 528 | igniter, |
| 471 529 | packages, |
| 472 530 | link_to_folder, |
| 473 531 | link_style, |
| 474 | - builtins |
| 532 | + builtins, |
| 533 | + builtins_link |
| 475 534 | ) |
| 476 535 | else |
| 477 536 | generate_usage_rules_inline(igniter, packages, builtins) |
| @@ -490,9 +549,13 @@ if Code.ensure_loaded?(Igniter) do | |
| 490 549 | {:error, _} -> File.read!(usage_rules_path) |
| 491 550 | end |
| 492 551 | |
| 552 | + description = get_package_description(name) |
| 553 | + description_part = if description == "", do: "", else: "_#{description}_\n\n" |
| 554 | + |
| 493 555 | {name, |
| 494 556 | "<!-- #{name}-start -->\n" <> |
| 495 557 | "## #{name} usage\n" <> |
| 558 | + description_part <> |
| 496 559 | content <> |
| 497 560 | "\n<!-- #{name}-end -->"} |
| 498 561 | end) |
| @@ -504,6 +567,8 @@ if Code.ensure_loaded?(Igniter) do | |
| 504 567 | |
| 505 568 | full_contents_for_new_file = |
| 506 569 | "<!-- usage-rules-start -->\n" <> |
| 570 | + usage_rules_header() <> |
| 571 | + "\n" <> |
| 507 572 | all_rules_content <> |
| 508 573 | "\n<!-- usage-rules-end -->" |
| 509 574 | |
| @@ -534,9 +599,17 @@ if Code.ensure_loaded?(Igniter) do | |
| 534 599 | end |
| 535 600 | end) |
| 536 601 | |> then(fn content -> |
| 602 | + # Ensure header is present |
| 603 | + content_with_header = |
| 604 | + if String.contains?(content, "<!-- usage-rules-header -->") do |
| 605 | + content |
| 606 | + else |
| 607 | + usage_rules_header() <> "\n" <> content |
| 608 | + end |
| 609 | + |
| 537 610 | prelude <> |
| 538 611 | "<!-- usage-rules-start -->\n" <> |
| 539 | - content <> |
| 612 | + content_with_header <> |
| 540 613 | "\n<!-- usage-rules-end -->" <> |
| 541 614 | postlude |
| 542 615 | end) |
| @@ -544,6 +617,8 @@ if Code.ensure_loaded?(Igniter) do | |
| 544 617 | _ -> |
| 545 618 | current_contents <> |
| 546 619 | "\n<!-- usage-rules-start -->\n" <> |
| 620 | + usage_rules_header() <> |
| 621 | + "\n" <> |
| 547 622 | all_rules_content <> |
| 548 623 | "\n<!-- usage-rules-end -->\n" |
| 549 624 | end |
| @@ -558,31 +633,36 @@ if Code.ensure_loaded?(Igniter) do | |
| 558 633 | packages, |
| 559 634 | folder_name, |
| 560 635 | link_style, |
| 561 | - builtins |
| 636 | + builtins, |
| 637 | + builtins_link |
| 562 638 | ) do |
| 563 639 | # Create individual files for each package in the folder (unless folder is "deps") |
| 564 640 | igniter = |
| 565 641 | if folder_name == "deps" do |
| 566 642 | igniter |
| 567 643 | else |
| 568 | - # Create builtin files in the target folder |
| 644 | + # Create builtin files in the target folder only if builtins_link is true |
| 569 645 | igniter = |
| 570 | - Enum.reduce(builtins, igniter, fn builtin, acc -> |
| 571 | - builtin_source_path = |
| 572 | - Path.join([:code.priv_dir(:usage_rules), "builtins", "#{builtin}.md"]) |
| 646 | + if builtins_link do |
| 647 | + Enum.reduce(builtins, igniter, fn builtin, acc -> |
| 648 | + builtin_source_path = |
| 649 | + Path.join([:code.priv_dir(:usage_rules), "builtins", "#{builtin}.md"]) |
| 573 650 | |
| 574 | - builtin_file_path = Path.join(folder_name, "#{builtin}.md") |
| 575 | - content = File.read!(builtin_source_path) |
| 651 | + builtin_file_path = Path.join(folder_name, "#{builtin}.md") |
| 652 | + content = File.read!(builtin_source_path) |
| 576 653 | |
| 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) |
| 654 | + Igniter.create_or_update_file( |
| 655 | + acc, |
| 656 | + builtin_file_path, |
| 657 | + content, |
| 658 | + fn source -> |
| 659 | + Rewrite.Source.update(source, :content, content) |
| 660 | + end |
| 661 | + ) |
| 662 | + end) |
| 663 | + else |
| 664 | + igniter |
| 665 | + end |
| 586 666 | |
| 587 667 | Enum.reduce(packages, igniter, fn {name, path}, acc -> |
| 588 668 | usage_rules_path = Path.join(path, "usage-rules.md") |
| @@ -618,43 +698,65 @@ if Code.ensure_loaded?(Igniter) do | |
| 618 698 | _ -> "[#{name} usage rules](#{folder_name}/#{name}.md)" |
| 619 699 | end |
| 620 700 | |
| 701 | + description = get_package_description(name) |
| 702 | + description_part = if description == "", do: "", else: "_#{description}_\n\n" |
| 703 | + |
| 621 704 | {name, |
| 622 705 | "<!-- #{name}-start -->\n" <> |
| 623 706 | "## #{name} usage\n" <> |
| 707 | + description_part <> |
| 624 708 | link_content <> |
| 625 709 | "\n<!-- #{name}-end -->"} |
| 626 710 | end) |
| 627 711 | |
| 628 712 | 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" |
| 713 | + if builtins_link do |
| 714 | + # If builtins_link is true, create links |
| 715 | + builtins |
| 716 | + |> Enum.map(fn builtin -> |
| 717 | + link_content = |
| 718 | + case {link_style, folder_name} do |
| 719 | + {"at", "deps"} -> |
| 720 | + "@deps/usage_rules/priv/builtins/#{builtin}.md" |
| 635 721 | |
| 636 | - {"at", _} -> |
| 637 | - "@#{folder_name}/#{builtin}.md" |
| 722 | + {"at", _} -> |
| 723 | + "@#{folder_name}/#{builtin}.md" |
| 638 724 | |
| 639 | - {_, "deps"} -> |
| 640 | - "[#{builtin} usage rules](deps/usage_rules/priv/builtins/#{builtin}.md)" |
| 725 | + {_, "deps"} -> |
| 726 | + "[#{builtin} usage rules](deps/usage_rules/priv/builtins/#{builtin}.md)" |
| 641 727 | |
| 642 | - _ -> |
| 643 | - "[#{builtin} usage rules](#{folder_name}/#{builtin}.md)" |
| 644 | - end |
| 728 | + _ -> |
| 729 | + "[#{builtin} usage rules](#{folder_name}/#{builtin}.md)" |
| 730 | + end |
| 645 731 | |
| 646 | - {String.to_atom(builtin), |
| 647 | - "<!-- #{builtin}-start -->\n" <> |
| 648 | - "## #{builtin} usage\n" <> |
| 649 | - link_content <> |
| 650 | - "\n<!-- #{builtin}-end -->"} |
| 651 | - end) |
| 732 | + description = |
| 733 | + case builtin do |
| 734 | + "elixir" -> "Core Elixir language features and standard library" |
| 735 | + "otp" -> "OTP (Open Telecom Platform) behaviors and patterns" |
| 736 | + _ -> "" |
| 737 | + end |
| 738 | + |
| 739 | + description_part = if description == "", do: "", else: "_#{description}_\n\n" |
| 740 | + |
| 741 | + {String.to_atom(builtin), |
| 742 | + "<!-- #{builtin}-start -->\n" <> |
| 743 | + "## #{builtin} usage\n" <> |
| 744 | + description_part <> |
| 745 | + link_content <> |
| 746 | + "\n<!-- #{builtin}-end -->"} |
| 747 | + end) |
| 748 | + else |
| 749 | + # If builtins_link is false (default), inline the content |
| 750 | + get_builtin_contents(builtins) |
| 751 | + end |
| 652 752 | |
| 653 753 | all_contents = package_contents ++ builtin_contents |
| 654 754 | all_rules_content = Enum.map_join(all_contents, "\n", &elem(&1, 1)) |
| 655 755 | |
| 656 756 | full_contents_for_new_file = |
| 657 757 | "<!-- usage-rules-start -->\n" <> |
| 758 | + usage_rules_header() <> |
| 759 | + "\n" <> |
| 658 760 | all_rules_content <> |
| 659 761 | "\n<!-- usage-rules-end -->" |
| 660 762 | |
| @@ -685,9 +787,17 @@ if Code.ensure_loaded?(Igniter) do | |
| 685 787 | end |
| 686 788 | end) |
| 687 789 | |> then(fn content -> |
| 790 | + # Ensure header is present |
| 791 | + content_with_header = |
| 792 | + if String.contains?(content, "<!-- usage-rules-header -->") do |
| 793 | + content |
| 794 | + else |
| 795 | + usage_rules_header() <> "\n" <> content |
| 796 | + end |
| 797 | + |
| 688 798 | prelude <> |
| 689 799 | "<!-- usage-rules-start -->\n" <> |
| 690 | - content <> |
| 800 | + content_with_header <> |
| 691 801 | "\n<!-- usage-rules-end -->" <> |
| 692 802 | postlude |
| 693 803 | end) |
| @@ -695,6 +805,8 @@ if Code.ensure_loaded?(Igniter) do | |
| 695 805 | _ -> |
| 696 806 | current_contents <> |
| 697 807 | "\n<!-- usage-rules-start -->\n" <> |
| 808 | + usage_rules_header() <> |
| 809 | + "\n" <> |
| 698 810 | all_rules_content <> |
| 699 811 | "\n<!-- usage-rules-end -->\n" |
| 700 812 | end |
| @@ -1,7 +1,7 @@ | |
| 1 1 | defmodule UsageRules.MixProject do |
| 2 2 | use Mix.Project |
| 3 3 | |
| 4 | - @version "0.1.10" |
| 4 | + @version "0.1.11" |
| 5 5 | @description """ |
| 6 6 | A dev tool for Elixir projects to gather LLM usage rules from dependencies |
| 7 7 | """ |