blockwatch-cli 0.13.0__tar.gz → 0.14.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.github/workflows/rust.yml +8 -0
  2. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.pre-commit-config.yaml +9 -0
  3. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/AGENTS.md +33 -22
  4. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/CHANGELOG.md +39 -0
  5. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/CLAUDE.md +33 -22
  6. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/Cargo.lock +1 -1
  7. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/Cargo.toml +1 -1
  8. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/PKG-INFO +1 -1
  9. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/README.md +2 -1
  10. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/blockwatch.toml +4 -31
  11. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/ci.md +2 -2
  12. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/cli.md +5 -5
  13. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/validators/README.md +3 -1
  14. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/validators/check-lua.md +4 -3
  15. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/validators/same-as.md +79 -16
  16. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/setup.py +1 -1
  17. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/block_parser.rs +115 -111
  18. blockwatch_cli-0.14.1/src/blocks.rs +1185 -0
  19. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/cli.rs +87 -93
  20. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/config.rs +51 -24
  21. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/diff_parser.rs +135 -66
  22. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/flags.rs +37 -27
  23. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/fs.rs +53 -78
  24. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/bash.rs +1 -1
  25. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/c.rs +1 -1
  26. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/c_sharp.rs +1 -1
  27. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/cmake.rs +1 -1
  28. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/cpp.rs +1 -1
  29. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/css.rs +1 -1
  30. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/dart.rs +1 -1
  31. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/dockerfile.rs +10 -9
  32. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/elixir.rs +1 -1
  33. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/go.rs +1 -1
  34. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/graphql.rs +1 -1
  35. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/groovy.rs +1 -1
  36. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/hcl.rs +1 -1
  37. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/html.rs +1 -1
  38. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/java.rs +1 -1
  39. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/javascript.rs +1 -3
  40. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/json.rs +2 -2
  41. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/kotlin.rs +1 -1
  42. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/lean.rs +4 -13
  43. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/lua.rs +1 -1
  44. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/makefile.rs +1 -1
  45. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/markdown.rs +22 -18
  46. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/mdx.rs +18 -27
  47. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/mod.rs +134 -30
  48. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/nix.rs +1 -1
  49. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/ocaml.rs +1 -1
  50. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/php.rs +9 -10
  51. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/proto.rs +1 -1
  52. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/python.rs +1 -1
  53. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/ruby.rs +1 -1
  54. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/rust.rs +1 -1
  55. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/scala.rs +1 -1
  56. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/sql.rs +1 -1
  57. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/starlark.rs +1 -1
  58. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/swift.rs +1 -1
  59. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/toml.rs +8 -8
  60. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/tsx.rs +1 -3
  61. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/typescript.rs +2 -2
  62. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/xml.rs +1 -1
  63. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/yaml.rs +6 -8
  64. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/lib.rs +89 -55
  65. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/path_arguments.rs +5 -3
  66. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/paths_glob.rs +2 -0
  67. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/repo_path.rs +11 -9
  68. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/report.rs +65 -59
  69. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/sarif.rs +49 -76
  70. blockwatch_cli-0.13.0/src/blocks.rs → blockwatch_cli-0.14.1/src/scan.rs +512 -1660
  71. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/settings.rs +131 -104
  72. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/skill.md +12 -28
  73. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/symbol_path.rs +3 -0
  74. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/symbols.rs +9 -12
  75. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/tag_parser.rs +18 -28
  76. blockwatch_cli-0.14.1/src/target_reference.rs +408 -0
  77. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/validators/affects.rs +79 -150
  78. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/validators/check_ai.rs +47 -73
  79. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/validators/check_lua.rs +64 -92
  80. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/validators/keep_sorted.rs +124 -155
  81. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/validators/keep_unique.rs +39 -71
  82. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/validators/line_count.rs +33 -71
  83. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/validators/line_pattern.rs +34 -71
  84. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/validators/mod.rs +332 -665
  85. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/validators/same_as.rs +712 -206
  86. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/violation_address.rs +49 -36
  87. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/virtual_blocks.rs +52 -38
  88. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/check_ai.rs +2 -2
  89. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/check_lua.rs +4 -4
  90. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/config.rs +1 -1
  91. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/keep_sorted.rs +4 -4
  92. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/keep_unique.rs +1 -1
  93. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/line_count.rs +1 -1
  94. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/line_pattern.rs +1 -1
  95. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/repository_root.rs +1 -1
  96. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/suppressions.rs +14 -0
  97. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.agents/.claude-plugin/plugin.json +0 -0
  98. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.agents/skills/blockwatch/SKILL.md +0 -0
  99. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.claude-plugin/marketplace.json +0 -0
  100. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.github/dependabot.yml +0 -0
  101. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.github/workflows/blockwatch.yml +0 -0
  102. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.github/workflows/codeql.yml +0 -0
  103. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.github/workflows/markdownlint.yml +0 -0
  104. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.github/workflows/pypi.yml +0 -0
  105. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.github/workflows/release.yml +0 -0
  106. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.github/workflows/trusted-checks.yml +0 -0
  107. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.gitignore +0 -0
  108. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.markdownlint-cli2.yaml +0 -0
  109. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/.pre-commit-hooks.yaml +0 -0
  110. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/LICENSE +0 -0
  111. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/SECURITY.md +0 -0
  112. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/clippy.toml +0 -0
  113. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/codecov.yml +0 -0
  114. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/demo.gif +0 -0
  115. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/dist-workspace.toml +0 -0
  116. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/agents.md +0 -0
  117. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/symbols.md +0 -0
  118. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/validators/affects.md +0 -0
  119. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/validators/check-ai.md +0 -0
  120. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/validators/keep-sorted.md +0 -0
  121. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/validators/keep-unique.md +0 -0
  122. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/validators/line-count.md +0 -0
  123. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/docs/validators/line-pattern.md +0 -0
  124. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/pypi/pyproject.toml +0 -0
  125. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/pyproject.toml +0 -0
  126. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/release.toml +0 -0
  127. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/scripts/check_language_count.lua +0 -0
  128. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/scripts/check_latest_gpt_nano_model.lua +0 -0
  129. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/language_parsers/json.scm +0 -0
  130. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/src/main.rs +0 -0
  131. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/affects.rs +0 -0
  132. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/common/empty.toml +0 -0
  133. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/common/mod.rs +0 -0
  134. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/encoding.rs +0 -0
  135. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/general.rs +0 -0
  136. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/list.rs +0 -0
  137. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/path_arguments.rs +0 -0
  138. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/same_as.rs +0 -0
  139. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/sarif.rs +0 -0
  140. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/skill.rs +0 -0
  141. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/symbol_references.rs +0 -0
  142. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/cross_file_source.rs +0 -0
  143. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/cross_file_target.md +0 -0
  144. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/custom_ext_source.javascript +0 -0
  145. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/custom_ext_target.javascript +0 -0
  146. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/dangling.md +0 -0
  147. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/missing_symbol.rs +0 -0
  148. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/more_added_than_deleted.py +0 -0
  149. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/multiline_tag_source.rs +0 -0
  150. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/multiline_tag_target.rs +0 -0
  151. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/nested_mapping_source.rs +0 -0
  152. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/nested_mapping_target.yaml +0 -0
  153. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/sibling.md +0 -0
  154. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/symbol_source.rs +0 -0
  155. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/symbol_target.json +0 -0
  156. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/table_source.rs +0 -0
  157. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/table_target.toml +0 -0
  158. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/tag_and_content_source.ts +0 -0
  159. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/tag_and_content_target.ts +0 -0
  160. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/whole_file_source.rs +0 -0
  161. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/affects/whole_file_target.json +0 -0
  162. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_ai/two_blocks.py +0 -0
  163. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/affects.lua +0 -0
  164. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/affects.py +0 -0
  165. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/affects_drift.py +0 -0
  166. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/blocking_io.lua +0 -0
  167. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/blocking_io.py +0 -0
  168. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/echo.lua +0 -0
  169. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/fail.lua +0 -0
  170. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/mode.py +0 -0
  171. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/os.lua +0 -0
  172. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/pathfmt/report_path.lua +0 -0
  173. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/pathfmt/source.py +0 -0
  174. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/pattern.py +0 -0
  175. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/pattern_many_matches.py +0 -0
  176. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/pattern_no_match.py +0 -0
  177. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/success.lua +0 -0
  178. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/check_lua/success_and_failure.py +0 -0
  179. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/config/glob_target/config.toml +0 -0
  180. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/config/glob_target/package.json +0 -0
  181. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/config/glob_target/plugins/blue/package.json +0 -0
  182. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/config/glob_target/plugins/green/package.json +0 -0
  183. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/config/glob_target/plugins/red/package.json +0 -0
  184. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/general/block_selection.py +0 -0
  185. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/general/custom_file_extension.javascript +0 -0
  186. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/general/custom_file_extension.python +0 -0
  187. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/general/disable_enable.py +0 -0
  188. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/general/paths/invalid.py +0 -0
  189. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/general/paths/subdir/nested_invalid.py +0 -0
  190. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/general/paths/valid.py +0 -0
  191. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/general/severity.py +0 -0
  192. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/hidden/.github/workflows/ci.yml +0 -0
  193. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/keep_sorted/orderings.py +0 -0
  194. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/keep_unique/basic_and_pattern.py +0 -0
  195. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/line_count/equality_and_greater_than.py +0 -0
  196. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/line_pattern/forbidden_text.py +0 -0
  197. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/line_pattern/hex_and_word_patterns.py +0 -0
  198. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/list/.hg/.gitkeep +0 -0
  199. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/list/a.py +0 -0
  200. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/list/b.py +0 -0
  201. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/positions/unicode_columns.py +0 -0
  202. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/custom_ext_source.javascript +0 -0
  203. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/custom_ext_target.javascript +0 -0
  204. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/package.json +0 -0
  205. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/paths/alpha/item.txt +0 -0
  206. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/paths/beta/item.txt +0 -0
  207. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/paths_and_block_source.md +0 -0
  208. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/paths_source.md +0 -0
  209. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/sibling.rs +0 -0
  210. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/source.rs +0 -0
  211. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/split_table_source.rs +0 -0
  212. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/split_table_target.toml +0 -0
  213. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/symbol_mismatch_source.rs +0 -0
  214. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/symbol_source.rs +0 -0
  215. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/target.md +0 -0
  216. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/whole_file_mismatch_source.rs +0 -0
  217. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/whole_file_source.rs +0 -0
  218. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/same_as/whole_file_target.json +0 -0
  219. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/sarif/advisory.py +0 -0
  220. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/sarif/clean.py +0 -0
  221. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/sarif/sorted.py +0 -0
  222. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/sarif/unnamed.py +0 -0
  223. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/commit_msg.txt +0 -0
  224. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/commit_msg_gone_a.txt +0 -0
  225. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/commit_msg_gone_b.txt +0 -0
  226. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/commit_msg_lowercase_trailer.txt +0 -0
  227. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/commit_msg_malformed.txt +0 -0
  228. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/commit_msg_with_noise.txt +0 -0
  229. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/dangling.py +0 -0
  230. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/sorted.py +0 -0
  231. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/symbol.py +0 -0
  232. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/symbol_target.json +0 -0
  233. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/two_rules.py +0 -0
  234. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/suppressions/unnamed.py +0 -0
  235. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/escapes_source.rs +0 -0
  236. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/escapes_target.json +0 -0
  237. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/filter_source.rs +0 -0
  238. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/filter_target.yaml +0 -0
  239. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/json_source.rs +0 -0
  240. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/json_target.json +0 -0
  241. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/jsonc_source.rs +0 -0
  242. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/jsonc_target.jsonc +0 -0
  243. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/markdown_source.rs +0 -0
  244. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/markdown_target.markdown +0 -0
  245. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/md_source.rs +0 -0
  246. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/md_target.md +0 -0
  247. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/mdx_source.rs +0 -0
  248. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/mdx_target.mdx +0 -0
  249. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/mixed_list_source.rs +0 -0
  250. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/mixed_list_target.json +0 -0
  251. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/toml_source.rs +0 -0
  252. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/toml_target.toml +0 -0
  253. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/yaml_source.rs +0 -0
  254. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/yaml_target.yaml +0 -0
  255. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/yml_source.rs +0 -0
  256. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/symbol_references/yml_target.yml +0 -0
  257. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/verbosity/affects/pair.py +0 -0
  258. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/verbosity/clean/counted.py +0 -0
  259. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/verbosity/clean/noop.py +0 -0
  260. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/verbosity/clean/notes.txt +0 -0
  261. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/verbosity/clean/sorted.py +0 -0
  262. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/verbosity/diff/two_blocks.py +0 -0
  263. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/verbosity/failing/unsorted.py +0 -0
  264. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/testdata/verbosity/mixed/both_kinds.py +0 -0
  265. {blockwatch_cli-0.13.0 → blockwatch_cli-0.14.1}/tests/verbosity.rs +0 -0
@@ -33,6 +33,14 @@ jobs:
33
33
  run: cargo fmt --check
34
34
  - name: Run Clippy lint
35
35
  run: cargo clippy --all-targets -- -D warnings
36
+ - name: Check the doc comments
37
+ # Nearly every item is private, so without the flag rustdoc would skip their docs.
38
+ run: cargo doc --no-deps --document-private-items
39
+ env:
40
+ RUSTDOCFLAGS: -D warnings
41
+ # `cargo llvm-cov` below does not run doctests.
42
+ - name: Run doctests
43
+ run: cargo test --doc
36
44
  - name: Run tests
37
45
  run: cargo llvm-cov --lcov --output-path coverage.lcov
38
46
  - name: Upload coverage report to Codecov
@@ -34,6 +34,15 @@ repos:
34
34
  types: [ rust ]
35
35
  stages: [ pre-commit ]
36
36
  pass_filenames: false
37
+ - id: cargo-doc
38
+ name: cargo doc
39
+ description: Check the doc comments with rustdoc, private items included.
40
+ # Nearly every item is private, so without the flag rustdoc would skip their docs.
41
+ entry: bash -c 'RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items'
42
+ language: system
43
+ types: [ rust ]
44
+ stages: [ pre-commit ]
45
+ pass_filenames: false
37
46
 
38
47
  # Rules and the set of files linted live in `.markdownlint-cli2.yaml`, which the tool picks up
39
48
  # from the repository root. The hook passes only the staged Markdown files, so a commit is judged
@@ -15,6 +15,8 @@ only the lines changed in a unified diff piped into it.
15
15
  cargo run -- list '<path or glob>' # print the blocks parsed from matching files, as JSON
16
16
  cargo llvm-cov # run the tests and report coverage
17
17
  cargo clippy --all-targets -- -D warnings # lint
18
+ cargo test --doc # run the examples in the doc comments (cargo llvm-cov skips them)
19
+ RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items # check the doc comments
18
20
  markdownlint-cli2 # lint Markdown (config: .markdownlint-cli2.yaml)
19
21
  pre-commit run --all-files # run every hook the repository installs
20
22
  ```
@@ -105,6 +107,8 @@ The goal is a test suite that fails when behavior changes. Running every line is
105
107
  - *Always* mutation-test a new test before you call the work done. For example, flip each comparison or boundary the
106
108
  change touched, run the tests, check that one fails, then undo the flip. If the tests pass either way, they are too
107
109
  weak.
110
+ - *Always* delete a scratch copy you made for mutation testing, with its own `target/` directory, as soon as the
111
+ mutation run ends. A `target/` directory takes gigabytes.
108
112
  - *Always* run the tests with coverage as the last check before you call the work done.
109
113
  - *Prefer* to read a coverage report (`cargo llvm-cov`) as a list of questions, not as a number to raise. An uncovered
110
114
  branch asks why nobody needed it. The answer is either a case worth testing or code worth deleting. *Never* add a
@@ -151,12 +155,14 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
151
155
 
152
156
  1. Parse the command-line flags (`flags.rs`, built with clap). The flags that `list` also takes are `global`, so they
153
157
  can go before or after `list`. The flags only validation uses are in `ValidationFlags`, so `list --help` doesn't
154
- show them.
158
+ show them. `Args::validate` rejects them for `list` and `skill`. A validation run then reads the `--suppress-from`
159
+ files, once. Both happen before stdin or the config file is read, so a bad flag is reported first.
155
160
  2. Find the repository root and build a `fs::FileSystemImpl` limited to it. Every file read goes through it, so a path
156
161
  in a block attribute can't reach outside the repository.
157
162
  3. Decide what the run works on. With `--diff`, a unified diff is read from stdin, and
158
- `diff_parser::line_changes_from_diff` turns it into a `HashMap<RepoPath, Vec<LineChange>>`. `--only-changed` also
159
- limits the run to the files in that diff. Without `--diff`, stdin is never read and no block counts as changed.
163
+ `diff_parser::line_changes_from_diff` turns it into `DiffChanges`: a sorted `LineChanges` for each file.
164
+ `--only-changed` also limits the run to the files in that diff. Without `--diff`, stdin is never read and no block
165
+ counts as changed.
160
166
  4. Resolve the run's settings. `config::read` reads `blockwatch.toml` from the repository root, or the file given by
161
167
  `--config`. `Args::raw_settings` returns the settings given as flags. `settings::Settings::resolve` validates both
162
168
  with the same rules and merges them: the ignore globs, the extension mappings and the validator selection.
@@ -175,9 +181,9 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
175
181
  touches, even when the diff does not touch that file. The result is a `blocks::FileBlocks` for each file, kept in a
176
182
  `validators::ValidationContext`. Each path or glob argument must select at least one parsed file, or the run
177
183
  stops. Under `--only-changed`, an argument that selects no file in the diff is checked against a walk of the tree.
178
- 7. `validators::detect_validators` passes each block to the `ValidatorDetector`s, one for each validator. A detector
179
- returns either `ValidatorType::Sync` or `ValidatorType::Async`. Async validators, such as `check-ai`, run on Tokio.
180
- The Tokio runtime only starts if at least one async validator is needed.
184
+ 7. `validators::detect_validators` asks `Validator::detect` about each block, once for each validator. It returns
185
+ either `ValidatorType::Sync` or `ValidatorType::Async`. Async validators, such as `check-ai`, run on Tokio. The Tokio
186
+ runtime only starts if at least one async validator is needed.
181
187
  8. Validators produce `Violation`s, each with a `ViolationRange` and a `BlockSeverity` (error or warning). A symbol
182
188
  reference such as `file#/path` is found with `symbols::resolve`. It searches the symbols that
183
189
  `LanguageParser::parse_symbols` finds in the target file, once per file. A missing or ambiguous symbol is a
@@ -191,12 +197,14 @@ prints `src/skill.md` right after step 1.
191
197
 
192
198
  The main modules:
193
199
 
194
- - `src/fs.rs` — the `FileSystem` and `PathChecker` traits, and their real implementations. Tests use these traits to
195
- swap in fakes: `FakeFileSystem` and `FakePathChecker` in `fs::test_utils`.
200
+ - `src/fs.rs` — the `FileSystem` trait and its real implementation, and `PathFilter`, which decides whether a run
201
+ reads a path: it must match an allow glob and no ignore glob. Tests swap in `FakeFileSystem` from `fs::test_utils`.
202
+ They build a real `PathFilter` with `PathFilter::allow_all()`, `allow_only` or `with_ignored_paths`.
196
203
  - `src/settings.rs` — `RawSettings` holds the settings read from one place: the config file or the flags. `Settings` is
197
- the validated and merged result. `RawSettings::validate` is the only place the rules for a setting live, so the
198
- flags and the config file can't drift apart. Its errors quote the bad value but don't show a line and column.
199
- Tracking where each value is written would cost more code than it saves the reader.
204
+ the validated and merged result. `RawSettings::parse` is the only place the rules for a setting live, so the flags
205
+ and the config file can't drift apart. It returns the parsed values, so nothing is parsed twice. Its errors quote the
206
+ bad value but don't show a line and column. Tracking where each value is written would cost more code than it saves
207
+ the reader.
200
208
  - `src/config.rs` — reads the config file into a `RawSettings` and the virtual blocks, using `toml_edit`'s serde
201
209
  support. Only the errors that `toml_edit` raises itself, such as an unknown key, show a line and column. An error
202
210
  about a `[[block]]` entry shows the line of its header, which `serde_spanned` gives.
@@ -217,8 +225,9 @@ The main modules:
217
225
  always the same map key.
218
226
  - `src/language_parsers/` — one tree-sitter grammar for each language. Each language has a `parser()` that returns a
219
227
  `LanguageParser`, which knows which syntax nodes are comments. `src/block_parser.rs` turns those comments into
220
- blocks. `language_parsers()` in `language_parsers/mod.rs` returns the map from extension to parser. **To add a
221
- language, add a module here and register it in that function.**
228
+ blocks. `language_parsers()` in `language_parsers/mod.rs` returns `LanguageParsers`: the parser of each extension.
229
+ Its `parser_for` picks the parser of a file, using the settings' extension mappings too. **To add a language, add a
230
+ module here and register it in that function.**
222
231
 
223
232
  Some languages also have symbols. Such a language has a `SymbolsParser`, which its `parser()` passes to
224
233
  `.with_symbols(...)`:
@@ -241,12 +250,13 @@ The main modules:
241
250
  a file with a syntax error, instead of resolving a path through tree-sitter's error recovery. It doesn't check the
242
251
  query itself.
243
252
  - `src/validators/` — one file for each validator (`affects`, `check_ai`, `check_lua`, `keep_sorted`, `keep_unique`,
244
- `line_count`, `line_pattern`, `same_as`). Each file exports a `*ValidatorDetector`. `validators/mod.rs` registers
245
- all of them.
246
- - `src/diff_parser.rs` — wraps `unidiff` to produce `LineChange`s. `range_intersects_any` decides whether they touch a
247
- range of the file, such as a block's start tag or a symbol's definition. A block's content has its own check in
248
- `blocks.rs`, because a deletion right where the content starts removes content. The results end up in
249
- `is_content_modified` and `is_start_tag_modified` on `BlockWithContext`, which decide which blocks count as changed.
253
+ `line_count`, `line_pattern`, `same_as`). Each file exports a `detect` function. `Validator::detect` in
254
+ `validators/mod.rs` calls them from one `match`.
255
+ - `src/diff_parser.rs` — wraps `unidiff` to produce `LineChange`s, kept in `LineChanges` for a file and in
256
+ `DiffChanges` for the whole diff. `LineChanges::touches` decides whether they touch a range of the file, such as a
257
+ block's start tag or a symbol's definition. A block's content has its own check in `blocks.rs`, because a deletion
258
+ right where the content starts removes content. The results end up in `is_content_modified` and
259
+ `is_start_tag_modified` on `BlockWithContext`, which decide which blocks count as changed.
250
260
 
251
261
  A block counts as changed only when its content or its start tag overlaps a `LineChange`. With
252
262
  `--diff --only-changed`, only changed blocks are validated. With `--diff` alone, every block is validated, but rules
@@ -276,9 +286,10 @@ They are not instructions for working on blockwatch, and changing them changes w
276
286
  guidance for working on blockwatch.
277
287
 
278
288
  `src/skill.md` must not hold block tags. A project that saves the skill parses it too, so each tag would become a block
279
- in that project. To point a block at a section of the skill, put the section between two plain comments, such as
280
- `<!-- json-paths -->` and `<!-- /json-paths -->`. Then add a `[[block]]` entry that matches them to `blockwatch.toml`,
281
- next to the others for `src/skill.md`.
289
+ in that project. To point a block at a section of the skill, use the section's headings, such as
290
+ `src/skill.md#/blockwatch/Symbols/JSON`. To point at only a part of a section, give that part a heading of its own.
291
+ When the section needs a rule of its own, such as a `same-as-pattern`, add a `[[block]]` entry for it to
292
+ `blockwatch.toml`, next to the others for `src/skill.md`.
282
293
 
283
294
  Each agent's local config directory is left out of git on purpose: `.claude/`, `.cursor/`, `.gemini/`, `.windsurf/`,
284
295
  `.aider*`, and the others listed in `.gitignore`. Don't commit agent-specific settings. Anything a future contributor
@@ -12,6 +12,45 @@ Releases up to and including v0.3.11 predate this file. Their notes live on the
12
12
 
13
13
  ## [Unreleased] - ReleaseDate
14
14
 
15
+ ## [0.14.1] - 2026-10-17
16
+
17
+ ### Changed
18
+
19
+ - The error for a wrong `keep-sorted`, `keep-sorted-format`, `same-as-mode`, `same-as-format` or `BLOCKWATCH_LUA_MODE`
20
+ value now has one form, such as `invalid keep-sorted "up" (expected asc or desc)`. It lists the valid values.
21
+ `keep-sorted-format` did not list them before.
22
+ - `same-as-mode`, `same-as-format` and `BLOCKWATCH_LUA_MODE` ignore upper and lower case, so `Numeric` and `Safe` work
23
+ like `numeric` and `safe`. `keep-sorted` and `keep-sorted-format` already did.
24
+ - A message about a block of `keep-sorted`, `keep-unique`, `line-count`, `line-pattern`, `check-ai` or `check-lua` now
25
+ says `at line 3` where it said `defined at line 3`, as the messages of `affects` and `same-as` always did. For
26
+ example: `Block src/a.rs:limits at line 3 has an out-of-order line 5 (asc)`.
27
+ - Every error now starts with a lowercase letter and has no period at the end. Before, about 30 started with a capital
28
+ letter or ended with a period. For example: `failed to read file "a.py"`, `could not find the repository root
29
+ directory` and `malformed block tag at line 1, column 4. If this is not meant as a tag, put it in backticks`. An
30
+ error that gives advice in a second sentence keeps that sentence. An error that starts with a name, such as `API` or
31
+ `Lua`, keeps its capital letter.
32
+
33
+ ## [0.14.0] - 2026-10-16
34
+
35
+ ### Added
36
+
37
+ - `same-as-pattern` can compare a key with its value. Every named group, such as `(?P<code>\d+)` and
38
+ `(?P<message>.+)`, takes part, and each match is one record. So a message that moves to another code fails, even
39
+ though the messages alone still agree. Both patterns need the same groups. Each group can have its own
40
+ `same-as-format`, such as `name:identifier,value:numeric`. See
41
+ [Compare several groups](docs/validators/same-as.md#compare-several-groups) and
42
+ [A format for each group](docs/validators/same-as.md#a-format-for-each-group)
43
+ ([#99](https://github.com/mennanov/blockwatch/issues/99)).
44
+
45
+ ### Changed
46
+
47
+ - A `same-as-pattern` with named groups other than `value` now compares them too. Before, only `value` counted, or the
48
+ whole match when the pattern had no `value` group. To keep comparing only `value`, change the other groups to
49
+ `(?:…)`.
50
+ - An error about the `same-as-mode`, `same-as-format` or `same-as-pattern` of a block with `same-as` or
51
+ `same-as-paths` now shows the block, such as `invalid same-as attributes on block src/config.rs:limits at line 3`.
52
+ Before, it showed only the bad value.
53
+
15
54
  ## [0.13.0] - 2026-10-15
16
55
 
17
56
  ### Added
@@ -15,6 +15,8 @@ only the lines changed in a unified diff piped into it.
15
15
  cargo run -- list '<path or glob>' # print the blocks parsed from matching files, as JSON
16
16
  cargo llvm-cov # run the tests and report coverage
17
17
  cargo clippy --all-targets -- -D warnings # lint
18
+ cargo test --doc # run the examples in the doc comments (cargo llvm-cov skips them)
19
+ RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items # check the doc comments
18
20
  markdownlint-cli2 # lint Markdown (config: .markdownlint-cli2.yaml)
19
21
  pre-commit run --all-files # run every hook the repository installs
20
22
  ```
@@ -105,6 +107,8 @@ The goal is a test suite that fails when behavior changes. Running every line is
105
107
  - *Always* mutation-test a new test before you call the work done. For example, flip each comparison or boundary the
106
108
  change touched, run the tests, check that one fails, then undo the flip. If the tests pass either way, they are too
107
109
  weak.
110
+ - *Always* delete a scratch copy you made for mutation testing, with its own `target/` directory, as soon as the
111
+ mutation run ends. A `target/` directory takes gigabytes.
108
112
  - *Always* run the tests with coverage as the last check before you call the work done.
109
113
  - *Prefer* to read a coverage report (`cargo llvm-cov`) as a list of questions, not as a number to raise. An uncovered
110
114
  branch asks why nobody needed it. The answer is either a case worth testing or code worth deleting. *Never* add a
@@ -151,12 +155,14 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
151
155
 
152
156
  1. Parse the command-line flags (`flags.rs`, built with clap). The flags that `list` also takes are `global`, so they
153
157
  can go before or after `list`. The flags only validation uses are in `ValidationFlags`, so `list --help` doesn't
154
- show them.
158
+ show them. `Args::validate` rejects them for `list` and `skill`. A validation run then reads the `--suppress-from`
159
+ files, once. Both happen before stdin or the config file is read, so a bad flag is reported first.
155
160
  2. Find the repository root and build a `fs::FileSystemImpl` limited to it. Every file read goes through it, so a path
156
161
  in a block attribute can't reach outside the repository.
157
162
  3. Decide what the run works on. With `--diff`, a unified diff is read from stdin, and
158
- `diff_parser::line_changes_from_diff` turns it into a `HashMap<RepoPath, Vec<LineChange>>`. `--only-changed` also
159
- limits the run to the files in that diff. Without `--diff`, stdin is never read and no block counts as changed.
163
+ `diff_parser::line_changes_from_diff` turns it into `DiffChanges`: a sorted `LineChanges` for each file.
164
+ `--only-changed` also limits the run to the files in that diff. Without `--diff`, stdin is never read and no block
165
+ counts as changed.
160
166
  4. Resolve the run's settings. `config::read` reads `blockwatch.toml` from the repository root, or the file given by
161
167
  `--config`. `Args::raw_settings` returns the settings given as flags. `settings::Settings::resolve` validates both
162
168
  with the same rules and merges them: the ignore globs, the extension mappings and the validator selection.
@@ -175,9 +181,9 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
175
181
  touches, even when the diff does not touch that file. The result is a `blocks::FileBlocks` for each file, kept in a
176
182
  `validators::ValidationContext`. Each path or glob argument must select at least one parsed file, or the run
177
183
  stops. Under `--only-changed`, an argument that selects no file in the diff is checked against a walk of the tree.
178
- 7. `validators::detect_validators` passes each block to the `ValidatorDetector`s, one for each validator. A detector
179
- returns either `ValidatorType::Sync` or `ValidatorType::Async`. Async validators, such as `check-ai`, run on Tokio.
180
- The Tokio runtime only starts if at least one async validator is needed.
184
+ 7. `validators::detect_validators` asks `Validator::detect` about each block, once for each validator. It returns
185
+ either `ValidatorType::Sync` or `ValidatorType::Async`. Async validators, such as `check-ai`, run on Tokio. The Tokio
186
+ runtime only starts if at least one async validator is needed.
181
187
  8. Validators produce `Violation`s, each with a `ViolationRange` and a `BlockSeverity` (error or warning). A symbol
182
188
  reference such as `file#/path` is found with `symbols::resolve`. It searches the symbols that
183
189
  `LanguageParser::parse_symbols` finds in the target file, once per file. A missing or ambiguous symbol is a
@@ -191,12 +197,14 @@ prints `src/skill.md` right after step 1.
191
197
 
192
198
  The main modules:
193
199
 
194
- - `src/fs.rs` — the `FileSystem` and `PathChecker` traits, and their real implementations. Tests use these traits to
195
- swap in fakes: `FakeFileSystem` and `FakePathChecker` in `fs::test_utils`.
200
+ - `src/fs.rs` — the `FileSystem` trait and its real implementation, and `PathFilter`, which decides whether a run
201
+ reads a path: it must match an allow glob and no ignore glob. Tests swap in `FakeFileSystem` from `fs::test_utils`.
202
+ They build a real `PathFilter` with `PathFilter::allow_all()`, `allow_only` or `with_ignored_paths`.
196
203
  - `src/settings.rs` — `RawSettings` holds the settings read from one place: the config file or the flags. `Settings` is
197
- the validated and merged result. `RawSettings::validate` is the only place the rules for a setting live, so the
198
- flags and the config file can't drift apart. Its errors quote the bad value but don't show a line and column.
199
- Tracking where each value is written would cost more code than it saves the reader.
204
+ the validated and merged result. `RawSettings::parse` is the only place the rules for a setting live, so the flags
205
+ and the config file can't drift apart. It returns the parsed values, so nothing is parsed twice. Its errors quote the
206
+ bad value but don't show a line and column. Tracking where each value is written would cost more code than it saves
207
+ the reader.
200
208
  - `src/config.rs` — reads the config file into a `RawSettings` and the virtual blocks, using `toml_edit`'s serde
201
209
  support. Only the errors that `toml_edit` raises itself, such as an unknown key, show a line and column. An error
202
210
  about a `[[block]]` entry shows the line of its header, which `serde_spanned` gives.
@@ -217,8 +225,9 @@ The main modules:
217
225
  always the same map key.
218
226
  - `src/language_parsers/` — one tree-sitter grammar for each language. Each language has a `parser()` that returns a
219
227
  `LanguageParser`, which knows which syntax nodes are comments. `src/block_parser.rs` turns those comments into
220
- blocks. `language_parsers()` in `language_parsers/mod.rs` returns the map from extension to parser. **To add a
221
- language, add a module here and register it in that function.**
228
+ blocks. `language_parsers()` in `language_parsers/mod.rs` returns `LanguageParsers`: the parser of each extension.
229
+ Its `parser_for` picks the parser of a file, using the settings' extension mappings too. **To add a language, add a
230
+ module here and register it in that function.**
222
231
 
223
232
  Some languages also have symbols. Such a language has a `SymbolsParser`, which its `parser()` passes to
224
233
  `.with_symbols(...)`:
@@ -241,12 +250,13 @@ The main modules:
241
250
  a file with a syntax error, instead of resolving a path through tree-sitter's error recovery. It doesn't check the
242
251
  query itself.
243
252
  - `src/validators/` — one file for each validator (`affects`, `check_ai`, `check_lua`, `keep_sorted`, `keep_unique`,
244
- `line_count`, `line_pattern`, `same_as`). Each file exports a `*ValidatorDetector`. `validators/mod.rs` registers
245
- all of them.
246
- - `src/diff_parser.rs` — wraps `unidiff` to produce `LineChange`s. `range_intersects_any` decides whether they touch a
247
- range of the file, such as a block's start tag or a symbol's definition. A block's content has its own check in
248
- `blocks.rs`, because a deletion right where the content starts removes content. The results end up in
249
- `is_content_modified` and `is_start_tag_modified` on `BlockWithContext`, which decide which blocks count as changed.
253
+ `line_count`, `line_pattern`, `same_as`). Each file exports a `detect` function. `Validator::detect` in
254
+ `validators/mod.rs` calls them from one `match`.
255
+ - `src/diff_parser.rs` — wraps `unidiff` to produce `LineChange`s, kept in `LineChanges` for a file and in
256
+ `DiffChanges` for the whole diff. `LineChanges::touches` decides whether they touch a range of the file, such as a
257
+ block's start tag or a symbol's definition. A block's content has its own check in `blocks.rs`, because a deletion
258
+ right where the content starts removes content. The results end up in `is_content_modified` and
259
+ `is_start_tag_modified` on `BlockWithContext`, which decide which blocks count as changed.
250
260
 
251
261
  A block counts as changed only when its content or its start tag overlaps a `LineChange`. With
252
262
  `--diff --only-changed`, only changed blocks are validated. With `--diff` alone, every block is validated, but rules
@@ -276,9 +286,10 @@ They are not instructions for working on blockwatch, and changing them changes w
276
286
  guidance for working on blockwatch.
277
287
 
278
288
  `src/skill.md` must not hold block tags. A project that saves the skill parses it too, so each tag would become a block
279
- in that project. To point a block at a section of the skill, put the section between two plain comments, such as
280
- `<!-- json-paths -->` and `<!-- /json-paths -->`. Then add a `[[block]]` entry that matches them to `blockwatch.toml`,
281
- next to the others for `src/skill.md`.
289
+ in that project. To point a block at a section of the skill, use the section's headings, such as
290
+ `src/skill.md#/blockwatch/Symbols/JSON`. To point at only a part of a section, give that part a heading of its own.
291
+ When the section needs a rule of its own, such as a `same-as-pattern`, add a `[[block]]` entry for it to
292
+ `blockwatch.toml`, next to the others for `src/skill.md`.
282
293
 
283
294
  Each agent's local config directory is left out of git on purpose: `.claude/`, `.cursor/`, `.gemini/`, `.windsurf/`,
284
295
  `.aider*`, and the others listed in `.gitignore`. Don't commit agent-specific settings. Anything a future contributor
@@ -274,7 +274,7 @@ checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
274
274
 
275
275
  [[package]]
276
276
  name = "blockwatch"
277
- version = "0.13.0"
277
+ version = "0.14.1"
278
278
  dependencies = [
279
279
  "anyhow",
280
280
  "assert-json-diff",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "blockwatch"
3
- version = "0.13.0"
3
+ version = "0.14.1"
4
4
  edition = "2024"
5
5
  repository = "https://github.com/mennanov/blockwatch"
6
6
  description = "Language agnostic linter that keeps your code and documentation in sync and valid"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blockwatch-cli
3
- Version: 0.13.0
3
+ Version: 0.14.1
4
4
  Summary: Language agnostic linter that keeps your code and documentation in sync and valid
5
5
  Keywords: linter,sync,drift,comments,agents
6
6
  Home-Page: https://github.com/mennanov/blockwatch
@@ -94,6 +94,7 @@ you'd otherwise have to nitpick in code review.
94
94
 
95
95
  <!-- <block name="available-validators"
96
96
  same-as="src/validators/mod.rs:validator-registry, docs/validators/README.md:validators-index"
97
+ same-as-format="identifier"
97
98
  same-as-pattern='^\|\s*\[`(?P<value>[a-z-]+)`\]'> -->
98
99
 
99
100
  | Validator | Description | Attributes |
@@ -214,7 +215,7 @@ exclusions, and custom extension mappings.
214
215
 
215
216
  ```yaml
216
217
  - repo: https://github.com/mennanov/blockwatch
217
- rev: v0.13.0 # Use latest release
218
+ rev: v0.14.1 # Use latest release
218
219
  hooks:
219
220
  - id: blockwatch
220
221
  ```
@@ -40,41 +40,14 @@ same-as = '''.github/workflows/codeql.yml,
40
40
  same-as-pattern = '(?:actions/checkout@|^)(?P<value>v\d+)$'
41
41
 
42
42
  # `blockwatch skill` prints src/skill.md, and projects save that output. A tag in that file would become a block in
43
- # every one of those projects. So the file marks its sections with plain comments, which are not tags, and these
44
- # entries make each marked section a named block.
43
+ # every one of those projects. So blocks point at its sections by their headings, and these entries hold the pattern
44
+ # that reads a section, which the section can't hold itself.
45
45
  [[block]]
46
- target = 'src/skill.md'
47
- target-pattern = '(?s)<!-- validator-catalog -->.*<!-- /validator-catalog -->'
48
- name = 'validator-catalog'
49
-
50
- [[block]]
51
- target = 'src/skill.md'
52
- target-pattern = '(?s)<!-- validator-reference -->.*<!-- /validator-reference -->'
46
+ target = 'src/skill.md#/blockwatch/Validator reference'
53
47
  name = 'validator-reference'
54
48
  same-as-pattern = '^\|\s*`(?P<value>[a-z-]+)`'
55
49
 
56
50
  [[block]]
57
- target = 'src/skill.md'
58
- target-pattern = '(?s)<!-- extensions-with-symbols -->.*<!-- /extensions-with-symbols -->'
51
+ target = 'src/skill.md#/blockwatch/Symbols/Files with symbols'
59
52
  name = 'extensions-with-symbols'
60
53
  same-as-pattern = '`\.?(?P<value>[A-Za-z]+)`'
61
-
62
- [[block]]
63
- target = 'src/skill.md'
64
- target-pattern = '(?s)<!-- json-paths -->.*<!-- /json-paths -->'
65
- name = 'json-paths'
66
-
67
- [[block]]
68
- target = 'src/skill.md'
69
- target-pattern = '(?s)<!-- toml-paths -->.*<!-- /toml-paths -->'
70
- name = 'toml-paths'
71
-
72
- [[block]]
73
- target = 'src/skill.md'
74
- target-pattern = '(?s)<!-- yaml-paths -->.*<!-- /yaml-paths -->'
75
- name = 'yaml-paths'
76
-
77
- [[block]]
78
- target = 'src/skill.md'
79
- target-pattern = '(?s)<!-- markdown-paths -->.*<!-- /markdown-paths -->'
80
- name = 'markdown-paths'
@@ -19,7 +19,7 @@ To use [pre-commit](https://pre-commit.com), add the hook to `.pre-commit-config
19
19
 
20
20
  ```yaml
21
21
  - repo: https://github.com/mennanov/blockwatch
22
- rev: v0.13.0 # Use latest release
22
+ rev: v0.14.1 # Use latest release
23
23
  hooks:
24
24
  - id: blockwatch
25
25
  ```
@@ -59,7 +59,7 @@ The `blockwatch` hook runs before the commit message exists, so it cannot see a
59
59
 
60
60
  ```yaml
61
61
  - repo: https://github.com/mennanov/blockwatch
62
- rev: v0.13.0 # Use latest release
62
+ rev: v0.14.1 # Use latest release
63
63
  hooks:
64
64
  - id: blockwatch-commit-msg
65
65
  ```
@@ -172,7 +172,7 @@ describe the input rather than the command that produced it, since a diff need n
172
172
 
173
173
  ```console
174
174
  $ git diff --no-prefix | blockwatch --diff
175
- Error: diff target "rules.py" has no recognized path prefix (a/, b/, i/, w/, c/, o/).
175
+ Error: diff target "rules.py" has no recognized path prefix (a/, b/, i/, w/, c/, o/)
176
176
  ```
177
177
 
178
178
  With Git, that means `--no-prefix`, `diff.noprefix`, or a custom `diff.srcPrefix` / `diff.dstPrefix`. To produce
@@ -555,15 +555,15 @@ Nothing else about the run changes: the same violations are found, and the exit
555
555
  "tool": {
556
556
  "driver": {
557
557
  "name": "blockwatch",
558
- "version": "0.13.0",
559
- "semanticVersion": "0.13.0",
558
+ "version": "0.14.1",
559
+ "semanticVersion": "0.14.1",
560
560
  "informationUri": "https://github.com/mennanov/blockwatch",
561
561
  "rules": [
562
562
  {
563
563
  "id": "keep-sorted",
564
564
  "name": "keep-sorted",
565
565
  "shortDescription": { "text": "Requires the lines of a block to stay in order." },
566
- "helpUri": "https://github.com/mennanov/blockwatch/blob/v0.13.0/docs/validators/keep-sorted.md"
566
+ "helpUri": "https://github.com/mennanov/blockwatch/blob/v0.14.1/docs/validators/keep-sorted.md"
567
567
  }
568
568
  ]
569
569
  }
@@ -574,7 +574,7 @@ Nothing else about the run changes: the same violations are found, and the exit
574
574
  "ruleId": "keep-sorted",
575
575
  "ruleIndex": 0,
576
576
  "level": "error",
577
- "message": { "text": "Block fruits.py:fruits defined at line 2 has an out-of-order line 4 (asc)" },
577
+ "message": { "text": "Block fruits.py:fruits at line 2 has an out-of-order line 4 (asc)" },
578
578
  "locations": [
579
579
  {
580
580
  "physicalLocation": {
@@ -23,8 +23,10 @@ between two references is fine.
23
23
 
24
24
  ## Which validator do I want?
25
25
 
26
- <!-- <block name="validators-index" affects="src/skill.md:validator-catalog"
26
+ <!-- <block name="validators-index"
27
+ affects="src/skill.md#/blockwatch/Annotating a new project/Where blocks add value (catalog)"
27
28
  same-as="src/validators/mod.rs:validator-registry, README.md:available-validators"
29
+ same-as-format="identifier"
28
30
  same-as-pattern='\[`(?P<value>[a-z-]+)`\]\('> -->
29
31
 
30
32
  | I want to... | Use |
@@ -158,7 +158,7 @@ end
158
158
  ```
159
159
 
160
160
  <!-- <block name="lua-safety-modes" same-as="src/validators/check_lua.rs:lua-safety-modes"
161
- same-as-pattern='^\|\s*`(?P<value>[a-z]+)`'> -->
161
+ same-as-format="identifier" same-as-pattern='^\|\s*`(?P<value>[a-z]+)`'> -->
162
162
 
163
163
  ## Safety modes
164
164
 
@@ -182,8 +182,9 @@ BLOCKWATCH_LUA_MODE=unsafe blockwatch
182
182
  | `safe` | All memory-safe libraries (including `io`, `os`, `package`) | Memory-safe - Allows file/OS access |
183
183
  | `unsafe` | All Lua standard libraries with no restrictions (including C modules) | Unsafe - Full system access |
184
184
 
185
- An empty value means `sandboxed`, the same as an unset variable. Any other value, such as `Safe`, stops a run that
186
- checks a `check-lua` block. The error lists the valid values.
185
+ An empty value means `sandboxed`, the same as an unset variable. Upper and lower case do not matter, so `Safe` works
186
+ like `safe`. Any other value, such as `sandbox`, stops a run that checks a `check-lua` block. The error lists the valid
187
+ values.
187
188
 
188
189
  If a script uses a library that is not available in the selected mode, the run stops with a Lua error. The block is not
189
190
  reported as a violation — the whole run fails. So a script that needs `io` or `os` will fail every `sandboxed` run,