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
4
files changed
+65
additions
-11
deletions
| @@ -12,6 +12,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline | |
| 12 12 | |
| 13 13 | <!-- changelog --> |
| 14 14 | |
| 15 | + ## v1.2.5 (2026-03-09) |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | + |
| 20 | + ### Bug Fixes: |
| 21 | + |
| 22 | + * --check false positive after disk round-trip due to eof_newline mismatch (#59) by Barnabas Jovanovics |
| 23 | + |
| 15 24 | ## v1.2.4 (2026-03-02) |
| @@ -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.2.4">>}. |
| 10 | + {<<"version">>,<<"1.2.5">>}. |
| 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">>}. |
| @@ -853,7 +853,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 853 853 | acc, |
| 854 854 | ref_path, |
| 855 855 | content, |
| 856 | - fn source -> Rewrite.Source.update(source, :content, content) end |
| 856 | + fn source -> update_source_content(source, content) end |
| 857 857 | ) |
| 858 858 | end |
| 859 859 | |
| @@ -868,7 +868,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 868 868 | inner_acc, |
| 869 869 | ref_path, |
| 870 870 | content, |
| 871 | - fn source -> Rewrite.Source.update(source, :content, content) end |
| 871 | + fn source -> update_source_content(source, content) end |
| 872 872 | ) |
| 873 873 | end) |
| 874 874 | end) |
| @@ -1038,7 +1038,7 @@ if Code.ensure_loaded?(Igniter) do | |
| 1038 1038 | acc2, |
| 1039 1039 | dst_path, |
| 1040 1040 | content, |
| 1041 | - fn source -> Rewrite.Source.update(source, :content, content) end |
| 1041 | + fn source -> update_source_content(source, content) end |
| 1042 1042 | ) |
| 1043 1043 | end) |
| 1044 1044 | end) |
| @@ -1346,20 +1346,50 @@ if Code.ensure_loaded?(Igniter) do | |
| 1346 1346 | if String.contains?(current_content, "<!-- usage-rules-skill-start -->") do |
| 1347 1347 | custom = extract_skill_custom_content(current_content) |
| 1348 1348 | |
| 1349 | - if custom != "" do |
| 1350 | - [new_frontmatter, new_managed] = |
| 1351 | - String.split(new_skill_md, "\n\n<!-- usage-rules-skill-start -->", parts: 2) |
| 1349 | + new_content = |
| 1350 | + if custom != "" do |
| 1351 | + [frontmatter, managed_body] = |
| 1352 | + String.split(new_skill_md, "\n\n<!-- usage-rules-skill-start -->", parts: 2) |
| 1352 1353 | |
| 1353 | - new_frontmatter <> |
| 1354 | - "\n\n" <> custom <> "\n\n<!-- usage-rules-skill-start -->" <> new_managed |
| 1354 | + frontmatter <> |
| 1355 | + "\n\n" <> custom <> "\n\n<!-- usage-rules-skill-start -->" <> managed_body |
| 1356 | + else |
| 1357 | + new_skill_md |
| 1358 | + end |
| 1359 | + |
| 1360 | + # Only return new content if the managed section actually changed. |
| 1361 | + # Rewrite.Source.write/2 applies eof_newline/1 which appends "\n" |
| 1362 | + # to disk content (see rewrite/lib/rewrite/source.ex, write/3 and |
| 1363 | + # eof_newline/1). The generated content may not end with "\n", so |
| 1364 | + # subsequent reads see a trailing-whitespace diff that isn't a real |
| 1365 | + # change. Comparing just the managed section avoids this. |
| 1366 | + if managed_section_changed?(current_content, new_content) do |
| 1367 | + new_content |
| 1355 1368 | else |
| 1356 | - new_skill_md |
| 1369 | + current_content |
| 1357 1370 | end |
| 1358 1371 | else |
| 1359 1372 | new_skill_md |
| 1360 1373 | end |
| 1361 1374 | end |
| 1362 1375 | |
| 1376 | + defp managed_section_changed?(current, new) do |
| 1377 | + extract_managed_section(current) != extract_managed_section(new) |
| 1378 | + end |
| 1379 | + |
| 1380 | + defp extract_managed_section(content) do |
| 1381 | + case String.split(content, "<!-- usage-rules-skill-start -->", parts: 2) do |
| 1382 | + [_, rest] -> |
| 1383 | + case String.split(rest, "<!-- usage-rules-skill-end -->", parts: 2) do |
| 1384 | + [managed, _] -> String.trim(managed) |
| 1385 | + _ -> String.trim(rest) |
| 1386 | + end |
| 1387 | + |
| 1388 | + _ -> |
| 1389 | + nil |
| 1390 | + end |
| 1391 | + end |
| 1392 | + |
| 1363 1393 | defp strip_managed_skill_content(content) do |
| 1364 1394 | # Remove managed-by metadata from frontmatter |
| 1365 1395 | content = String.replace(content, ~r/metadata:\n\s+managed-by: usage-rules\n/, "") |
| @@ -1385,6 +1415,21 @@ if Code.ensure_loaded?(Igniter) do | |
| 1385 1415 | String.replace(content, ~r/\A\s*<!--\s*\n(?:.*?SPDX-.*?\n)*.*?-->\s*\n*/s, "") |
| 1386 1416 | end |
| 1387 1417 | |
| 1418 | + # Updates a reference file's content, ignoring trailing whitespace differences. |
| 1419 | + # Reference files have no managed-section markers — the entire file is synced |
| 1420 | + # content. Rewrite.Source.write/2 applies eof_newline/1 (see |
| 1421 | + # rewrite/lib/rewrite/source.ex) which appends "\n" when writing to disk, |
| 1422 | + # but dep content may not end with one, causing false diffs on subsequent syncs. |
| 1423 | + defp update_source_content(source, new_content) do |
| 1424 | + current = Rewrite.Source.get(source, :content) |
| 1425 | + |
| 1426 | + if String.trim_trailing(current) == String.trim_trailing(new_content) do |
| 1427 | + source |
| 1428 | + else |
| 1429 | + Rewrite.Source.update(source, :content, new_content) |
| 1430 | + end |
| 1431 | + end |
| 1432 | + |
| 1388 1433 | defp format_yaml_string(str) do |
| 1389 1434 | str = String.trim(str) |
| @@ -5,7 +5,7 @@ | |
| 5 5 | defmodule UsageRules.MixProject do |
| 6 6 | use Mix.Project |
| 7 7 | |
| 8 | - @version "1.2.4" |
| 8 | + @version "1.2.5" |
| 9 9 | @description """ |
| 10 10 | A config-driven dev tool for Elixir projects to manage AGENTS.md files and agent skills from dependencies |
| 11 11 | """ |