blockwatch-cli 0.11.2__tar.gz → 0.12.0__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 (258) hide show
  1. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/AGENTS.md +3 -2
  2. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/CHANGELOG.md +40 -0
  3. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/CLAUDE.md +3 -2
  4. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/Cargo.lock +1 -1
  5. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/Cargo.toml +1 -5
  6. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/PKG-INFO +1 -1
  7. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/README.md +14 -14
  8. blockwatch_cli-0.12.0/blockwatch.toml +40 -0
  9. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/ci.md +4 -6
  10. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/cli.md +15 -6
  11. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/validators/README.md +1 -1
  12. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/validators/keep-sorted.md +2 -1
  13. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/validators/keep-unique.md +2 -1
  14. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/validators/same-as.md +3 -2
  15. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/release.toml +3 -3
  16. blockwatch_cli-0.12.0/scripts/check_language_count.lua +19 -0
  17. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/setup.py +2 -2
  18. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/block_parser.rs +13 -0
  19. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/blocks.rs +10 -4
  20. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/cli.rs +10 -8
  21. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/config.rs +5 -0
  22. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/flags.rs +35 -3
  23. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/fs.rs +1 -1
  24. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/bash.rs +2 -1
  25. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/cmake.rs +7 -2
  26. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/css.rs +5 -4
  27. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/json.rs +4 -2
  28. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/lean.rs +2 -0
  29. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/lua.rs +6 -1
  30. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/markdown.rs +87 -36
  31. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/mdx.rs +18 -6
  32. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/mod.rs +56 -31
  33. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/ocaml.rs +2 -2
  34. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/php.rs +3 -3
  35. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/ruby.rs +7 -1
  36. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/rust.rs +6 -5
  37. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/scala.rs +15 -0
  38. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/sql.rs +6 -5
  39. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/toml.rs +8 -0
  40. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/yaml.rs +24 -4
  41. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/lib.rs +17 -1
  42. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/paths_glob.rs +1 -0
  43. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/repo_path.rs +2 -2
  44. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/sarif.rs +9 -15
  45. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/skill.md +4 -0
  46. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/symbol_path.rs +35 -4
  47. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/symbols.rs +11 -3
  48. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/tag_parser.rs +31 -4
  49. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/validators/check_ai.rs +20 -1
  50. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/validators/check_lua.rs +33 -0
  51. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/validators/keep_unique.rs +19 -0
  52. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/validators/line_pattern.rs +8 -0
  53. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/validators/mod.rs +150 -32
  54. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/validators/same_as.rs +22 -2
  55. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/virtual_blocks.rs +33 -5
  56. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/general.rs +25 -0
  57. blockwatch_cli-0.11.2/blockwatch.toml +0 -6
  58. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.agents/.claude-plugin/plugin.json +0 -0
  59. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.agents/skills/blockwatch/SKILL.md +0 -0
  60. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.claude-plugin/marketplace.json +0 -0
  61. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.github/dependabot.yml +0 -0
  62. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.github/workflows/blockwatch.yml +0 -0
  63. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.github/workflows/codeql.yml +0 -0
  64. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.github/workflows/markdownlint.yml +0 -0
  65. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.github/workflows/pypi.yml +0 -0
  66. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.github/workflows/release.yml +0 -0
  67. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.github/workflows/rust.yml +0 -0
  68. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.github/workflows/trusted-checks.yml +0 -0
  69. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.gitignore +0 -0
  70. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.markdownlint-cli2.yaml +0 -0
  71. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.pre-commit-config.yaml +0 -0
  72. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/.pre-commit-hooks.yaml +0 -0
  73. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/LICENSE +0 -0
  74. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/SECURITY.md +0 -0
  75. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/clippy.toml +0 -0
  76. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/codecov.yml +0 -0
  77. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/demo.gif +0 -0
  78. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/dist-workspace.toml +0 -0
  79. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/agents.md +0 -0
  80. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/symbols.md +0 -0
  81. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/validators/affects.md +0 -0
  82. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/validators/check-ai.md +0 -0
  83. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/validators/check-lua.md +0 -0
  84. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/validators/line-count.md +0 -0
  85. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/docs/validators/line-pattern.md +0 -0
  86. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/pypi/pyproject.toml +0 -0
  87. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/pyproject.toml +0 -0
  88. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/scripts/check_latest_gpt_nano_model.lua +0 -0
  89. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/diff_parser.rs +0 -0
  90. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/c.rs +0 -0
  91. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/c_sharp.rs +0 -0
  92. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/cpp.rs +0 -0
  93. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/dart.rs +0 -0
  94. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/dockerfile.rs +0 -0
  95. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/elixir.rs +0 -0
  96. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/go.rs +0 -0
  97. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/graphql.rs +0 -0
  98. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/groovy.rs +0 -0
  99. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/hcl.rs +0 -0
  100. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/html.rs +0 -0
  101. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/java.rs +0 -0
  102. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/javascript.rs +0 -0
  103. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/json.scm +0 -0
  104. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/kotlin.rs +0 -0
  105. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/makefile.rs +0 -0
  106. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/nix.rs +0 -0
  107. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/proto.rs +0 -0
  108. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/python.rs +0 -0
  109. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/starlark.rs +0 -0
  110. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/swift.rs +0 -0
  111. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/tsx.rs +0 -0
  112. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/typescript.rs +0 -0
  113. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/language_parsers/xml.rs +0 -0
  114. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/main.rs +0 -0
  115. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/path_arguments.rs +0 -0
  116. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/report.rs +0 -0
  117. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/settings.rs +0 -0
  118. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/validators/affects.rs +0 -0
  119. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/validators/keep_sorted.rs +0 -0
  120. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/validators/line_count.rs +0 -0
  121. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/src/violation_address.rs +0 -0
  122. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/affects.rs +0 -0
  123. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/check_ai.rs +0 -0
  124. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/check_lua.rs +0 -0
  125. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/common/empty.toml +0 -0
  126. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/common/mod.rs +0 -0
  127. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/config.rs +0 -0
  128. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/encoding.rs +0 -0
  129. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/keep_sorted.rs +0 -0
  130. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/keep_unique.rs +0 -0
  131. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/line_count.rs +0 -0
  132. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/line_pattern.rs +0 -0
  133. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/list.rs +0 -0
  134. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/path_arguments.rs +0 -0
  135. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/repository_root.rs +0 -0
  136. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/same_as.rs +0 -0
  137. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/sarif.rs +0 -0
  138. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/skill.rs +0 -0
  139. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/suppressions.rs +0 -0
  140. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/symbol_references.rs +0 -0
  141. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/cross_file_source.rs +0 -0
  142. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/cross_file_target.md +0 -0
  143. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/custom_ext_source.javascript +0 -0
  144. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/custom_ext_target.javascript +0 -0
  145. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/dangling.md +0 -0
  146. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/missing_symbol.rs +0 -0
  147. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/more_added_than_deleted.py +0 -0
  148. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/multiline_tag_source.rs +0 -0
  149. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/multiline_tag_target.rs +0 -0
  150. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/nested_mapping_source.rs +0 -0
  151. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/nested_mapping_target.yaml +0 -0
  152. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/sibling.md +0 -0
  153. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/symbol_source.rs +0 -0
  154. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/symbol_target.json +0 -0
  155. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/table_source.rs +0 -0
  156. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/table_target.toml +0 -0
  157. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/tag_and_content_source.ts +0 -0
  158. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/tag_and_content_target.ts +0 -0
  159. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/whole_file_source.rs +0 -0
  160. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/affects/whole_file_target.json +0 -0
  161. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_ai/two_blocks.py +0 -0
  162. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/affects.lua +0 -0
  163. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/affects.py +0 -0
  164. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/affects_drift.py +0 -0
  165. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/blocking_io.lua +0 -0
  166. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/blocking_io.py +0 -0
  167. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/echo.lua +0 -0
  168. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/fail.lua +0 -0
  169. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/mode.py +0 -0
  170. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/os.lua +0 -0
  171. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/pathfmt/report_path.lua +0 -0
  172. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/pathfmt/source.py +0 -0
  173. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/pattern.py +0 -0
  174. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/pattern_many_matches.py +0 -0
  175. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/pattern_no_match.py +0 -0
  176. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/success.lua +0 -0
  177. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/check_lua/success_and_failure.py +0 -0
  178. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/config/glob_target/config.toml +0 -0
  179. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/config/glob_target/package.json +0 -0
  180. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/config/glob_target/plugins/blue/package.json +0 -0
  181. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/config/glob_target/plugins/green/package.json +0 -0
  182. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/config/glob_target/plugins/red/package.json +0 -0
  183. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/general/block_selection.py +0 -0
  184. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/general/custom_file_extension.javascript +0 -0
  185. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/general/custom_file_extension.python +0 -0
  186. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/general/disable_enable.py +0 -0
  187. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/general/paths/invalid.py +0 -0
  188. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/general/paths/subdir/nested_invalid.py +0 -0
  189. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/general/paths/valid.py +0 -0
  190. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/general/severity.py +0 -0
  191. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/hidden/.github/workflows/ci.yml +0 -0
  192. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/keep_sorted/orderings.py +0 -0
  193. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/keep_unique/basic_and_pattern.py +0 -0
  194. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/line_count/equality_and_greater_than.py +0 -0
  195. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/line_pattern/forbidden_text.py +0 -0
  196. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/line_pattern/hex_and_word_patterns.py +0 -0
  197. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/list/.hg/.gitkeep +0 -0
  198. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/list/a.py +0 -0
  199. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/list/b.py +0 -0
  200. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/positions/unicode_columns.py +0 -0
  201. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/custom_ext_source.javascript +0 -0
  202. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/custom_ext_target.javascript +0 -0
  203. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/package.json +0 -0
  204. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/paths/alpha/item.txt +0 -0
  205. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/paths/beta/item.txt +0 -0
  206. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/paths_and_block_source.md +0 -0
  207. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/paths_source.md +0 -0
  208. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/sibling.rs +0 -0
  209. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/source.rs +0 -0
  210. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/split_table_source.rs +0 -0
  211. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/split_table_target.toml +0 -0
  212. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/symbol_mismatch_source.rs +0 -0
  213. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/symbol_source.rs +0 -0
  214. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/target.md +0 -0
  215. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/whole_file_mismatch_source.rs +0 -0
  216. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/whole_file_source.rs +0 -0
  217. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/same_as/whole_file_target.json +0 -0
  218. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/sarif/advisory.py +0 -0
  219. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/sarif/clean.py +0 -0
  220. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/sarif/sorted.py +0 -0
  221. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/sarif/unnamed.py +0 -0
  222. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/commit_msg.txt +0 -0
  223. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/commit_msg_gone_a.txt +0 -0
  224. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/commit_msg_gone_b.txt +0 -0
  225. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/commit_msg_lowercase_trailer.txt +0 -0
  226. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/commit_msg_malformed.txt +0 -0
  227. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/commit_msg_with_noise.txt +0 -0
  228. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/dangling.py +0 -0
  229. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/sorted.py +0 -0
  230. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/symbol.py +0 -0
  231. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/symbol_target.json +0 -0
  232. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/two_rules.py +0 -0
  233. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/suppressions/unnamed.py +0 -0
  234. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/escapes_source.rs +0 -0
  235. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/escapes_target.json +0 -0
  236. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/filter_source.rs +0 -0
  237. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/filter_target.yaml +0 -0
  238. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/json_source.rs +0 -0
  239. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/json_target.json +0 -0
  240. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/jsonc_source.rs +0 -0
  241. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/jsonc_target.jsonc +0 -0
  242. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/mixed_list_source.rs +0 -0
  243. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/mixed_list_target.json +0 -0
  244. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/toml_source.rs +0 -0
  245. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/toml_target.toml +0 -0
  246. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/yaml_source.rs +0 -0
  247. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/yaml_target.yaml +0 -0
  248. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/yml_source.rs +0 -0
  249. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/symbol_references/yml_target.yml +0 -0
  250. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/verbosity/affects/pair.py +0 -0
  251. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/verbosity/clean/counted.py +0 -0
  252. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/verbosity/clean/noop.py +0 -0
  253. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/verbosity/clean/notes.txt +0 -0
  254. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/verbosity/clean/sorted.py +0 -0
  255. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/verbosity/diff/two_blocks.py +0 -0
  256. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/verbosity/failing/unsorted.py +0 -0
  257. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/testdata/verbosity/mixed/both_kinds.py +0 -0
  258. {blockwatch_cli-0.11.2 → blockwatch_cli-0.12.0}/tests/verbosity.rs +0 -0
@@ -125,8 +125,9 @@ Source files here contain real `<block ...>` tags. Two runs check them:
125
125
  the code here.
126
126
 
127
127
  The hook and CI read `blockwatch.toml`. It skips `tests/testdata/`, where the fixtures break rules on purpose. It also
128
- turns off `check-lua`, because the `check-lua` block here needs the network. `.github/workflows/trusted-checks.yml`
129
- runs that block on a schedule.
128
+ skips the `latest-model` block, because its `check-lua` script needs the network. `.github/workflows/trusted-checks.yml`
129
+ runs that block on a schedule. The `[[block]]` entries in `blockwatch.toml` hold rules too, with no tag in the file
130
+ they check, such as the version and the dependencies in `Cargo.toml`.
130
131
 
131
132
  No test checks these blocks, so `cargo test` can pass while the hook fails. `tests/general.rs` also runs the linter on
132
133
  the whole tree, but with an empty config, so the fixtures fail that run on purpose. To check the blocks before you
@@ -12,6 +12,46 @@ 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.12.0] - 2026-10-15
16
+
17
+ ### Changed
18
+
19
+ - A `blockwatch-suppress:` line with no address now fails the run with `invalid suppression address`. Before,
20
+ blockwatch ignored it.
21
+ - Messages now show a non-ASCII key in a symbol path as written, such as `data.toml#/täble/key`. Before, they showed
22
+ `data.toml#/t%C3%A4ble/key`. An `affects` or `same-as` violation on such a key gets a new hash, so update any
23
+ `--suppress` address with four parts that points at it.
24
+
25
+ ### Fixed
26
+
27
+ - `--suppress-from` no longer crashes on a line with a non-ASCII character, such as `❌` or `—`, near its start.
28
+ Before, such a line could make blockwatch exit with code 101, even when the code had no violations
29
+ ([#172](https://github.com/mennanov/blockwatch/issues/172)).
30
+ - blockwatch no longer crashes on a file that holds a tag and one of these:
31
+ - in Scala, a `/*` comment that is never closed;
32
+ - in Markdown, a `[//]:` line with no title, such as `[//]: #`, or with a quote in its destination, such as
33
+ `[//]: it's (text)`.
34
+ - In Markdown, a tag in a `[//]:` title that starts on the next line is now reported on that line. Before, it was
35
+ reported on the `[//]:` line.
36
+ - A Lua error in a `check-lua` script now shows the script and the line, such as `check.lua:3:`. Before, it showed a
37
+ file of blockwatch's own source code: `src/validators/check_lua.rs:309:3:`.
38
+ - `blockwatch list`, the JSON violations and the SARIF log now come out in the same order on every run. Files are
39
+ sorted by path, and the violations in a file by line, column and validator. Before, the order changed from run to
40
+ run.
41
+ - A pattern with its `value` group in one branch, such as `^(?:none|(?P<value>\w+))$`, now skips a match of the other
42
+ branch, such as `none`. This applies to `keep-sorted-pattern`, `keep-unique`, `same-as-pattern`, `check-ai-pattern`
43
+ and `check-lua-pattern`. Before, such a match gave its whole text as a value, so `keep-unique` could report a false
44
+ duplicate ([#173](https://github.com/mennanov/blockwatch/issues/173)). A `target-pattern` match like that now stops
45
+ the run. Before, the block's content was the whole match.
46
+
47
+ ## [0.11.3] - 2026-10-15
48
+
49
+ ### Fixed
50
+
51
+ - A block with `keep-unique-pattern` now fails the run with ``unrecognized attribute `keep-unique-pattern` ``. Before,
52
+ blockwatch accepted the attribute and ignored it, so `keep-unique` compared whole lines. Put the regex in
53
+ `keep-unique` itself: `keep-unique="(?P<value>\w+)"`.
54
+
15
55
  ## [0.11.2] - 2026-10-15
16
56
 
17
57
  ### Added
@@ -125,8 +125,9 @@ Source files here contain real `<block ...>` tags. Two runs check them:
125
125
  the code here.
126
126
 
127
127
  The hook and CI read `blockwatch.toml`. It skips `tests/testdata/`, where the fixtures break rules on purpose. It also
128
- turns off `check-lua`, because the `check-lua` block here needs the network. `.github/workflows/trusted-checks.yml`
129
- runs that block on a schedule.
128
+ skips the `latest-model` block, because its `check-lua` script needs the network. `.github/workflows/trusted-checks.yml`
129
+ runs that block on a schedule. The `[[block]]` entries in `blockwatch.toml` hold rules too, with no tag in the file
130
+ they check, such as the version and the dependencies in `Cargo.toml`.
130
131
 
131
132
  No test checks these blocks, so `cargo test` can pass while the hook fails. `tests/general.rs` also runs the linter on
132
133
  the whole tree, but with an empty config, so the fixtures fail that run on purpose. To check the blocks before you
@@ -274,7 +274,7 @@ checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
274
274
 
275
275
  [[package]]
276
276
  name = "blockwatch"
277
- version = "0.11.2"
277
+ version = "0.12.0"
278
278
  dependencies = [
279
279
  "anyhow",
280
280
  "assert-json-diff",
@@ -1,8 +1,6 @@
1
1
  [package]
2
2
  name = "blockwatch"
3
- # <block name="crate-version" same-as="docs/cli.md:sarif-example" same-as-mode="subset" same-as-pattern='version = "(?P<value>\d+\.\d+\.\d+)"'>
4
- version = "0.11.2"
5
- # </block>
3
+ version = "0.12.0"
6
4
  edition = "2024"
7
5
  repository = "https://github.com/mennanov/blockwatch"
8
6
  description = "Language agnostic linter that keeps your code and documentation in sync and valid"
@@ -43,7 +41,6 @@ strum_macros = "0.28"
43
41
  tokio = { version = "1.52", features = ["full"] }
44
42
  toml_edit = { version = "0.25", default-features = false, features = ["parse", "serde"] }
45
43
  # The grammar crates the language parsers are built from.
46
- # <block keep-sorted keep-unique>
47
44
  tree-sitter = "0.27.0"
48
45
  tree-sitter-bash = "0.25"
49
46
  tree-sitter-c = "0.24"
@@ -81,7 +78,6 @@ tree-sitter-toml-ng = "0.7"
81
78
  tree-sitter-typescript = "0.23"
82
79
  tree-sitter-xml = "0.7"
83
80
  tree-sitter-yaml = "0.7"
84
- # </block>
85
81
  unidiff = "0.4.1"
86
82
  winnow = "1.0"
87
83
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blockwatch-cli
3
- Version: 0.11.2
3
+ Version: 0.12.0
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
@@ -96,16 +96,16 @@ have to nitpick in code review.
96
96
  same-as="src/validators/mod.rs:validator-registry, docs/validators/README.md:validators-index"
97
97
  same-as-pattern='^\|\s*\[`(?P<value>[a-z-]+)`\]'> -->
98
98
 
99
- | Validator | Description | Attributes |
100
- |---------------------------------------------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------|
101
- | [`affects`](docs/validators/affects.md) | Forces linked blocks to be edited together (e.g. code and its docs); needs a diff | `affects` |
102
- | [`same-as`](docs/validators/same-as.md) | Asserts two or more blocks hold the same value, across languages and formats; no diff needed | `same-as`, `same-as-pattern`, `same-as-mode`, `same-as-format` |
103
- | [`keep-sorted`](docs/validators/keep-sorted.md) | Enforces alphabetical or numerical ordering on list items | `keep-sorted`, `keep-sorted-pattern`, `keep-sorted-format` |
104
- | [`keep-unique`](docs/validators/keep-unique.md) | Prevents duplicate lines within a block | `keep-unique` |
105
- | [`line-pattern`](docs/validators/line-pattern.md) | Enforces that every line matches a regex, or that no line matches a forbidden one | `line-pattern`, `line-pattern-forbidden` |
106
- | [`line-count`](docs/validators/line-count.md) | Enforces lower or upper bounds on the number of lines in a block | `line-count` |
107
- | [`check-ai`](docs/validators/check-ai.md) | Validates content against natural language rules using an LLM | `check-ai`, `check-ai-pattern` |
108
- | [`check-lua`](docs/validators/check-lua.md) | Runs custom validation logic written in Lua | `check-lua`, `check-lua-pattern`, `check-lua-timeout` |
99
+ | Validator | Description | Attributes |
100
+ |---------------------------------------------------|----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
101
+ | [`affects`](docs/validators/affects.md) | Forces linked blocks to be edited together (e.g. code and its docs); needs a diff | `affects` |
102
+ | [`same-as`](docs/validators/same-as.md) | Asserts two or more blocks hold the same value, across languages and formats; no diff needed | `same-as`, `same-as-paths`, `same-as-pattern`, `same-as-mode`, `same-as-format` |
103
+ | [`keep-sorted`](docs/validators/keep-sorted.md) | Enforces alphabetical or numerical ordering on list items | `keep-sorted`, `keep-sorted-pattern`, `keep-sorted-format` |
104
+ | [`keep-unique`](docs/validators/keep-unique.md) | Prevents duplicate lines within a block | `keep-unique` |
105
+ | [`line-pattern`](docs/validators/line-pattern.md) | Enforces that every line matches a regex, or that no line matches a forbidden one | `line-pattern`, `line-pattern-forbidden` |
106
+ | [`line-count`](docs/validators/line-count.md) | Enforces lower or upper bounds on the number of lines in a block | `line-count` |
107
+ | [`check-ai`](docs/validators/check-ai.md) | Validates content against natural language rules using an LLM | `check-ai`, `check-ai-pattern` |
108
+ | [`check-lua`](docs/validators/check-lua.md) | Runs custom validation logic written in Lua | `check-lua`, `check-lua-pattern`, `check-lua-timeout` |
109
109
 
110
110
  <!-- </block> -->
111
111
 
@@ -210,12 +210,11 @@ exclusions, and custom extension mappings.
210
210
 
211
211
  **pre-commit** (`.pre-commit-config.yaml`):
212
212
 
213
- <!-- <block name="pre-commit-rev" same-as="Cargo.toml:crate-version" same-as-mode="subset"
214
- same-as-pattern='rev: v(?P<value>\d+\.\d+\.\d+)'> -->
213
+ <!-- <block name="pre-commit-rev" same-as-pattern='rev: v(?P<value>\d+\.\d+\.\d+)'> -->
215
214
 
216
215
  ```yaml
217
216
  - repo: https://github.com/mennanov/blockwatch
218
- rev: v0.11.2 # Use latest release
217
+ rev: v0.12.0 # Use latest release
219
218
  hooks:
220
219
  - id: blockwatch
221
220
  ```
@@ -240,7 +239,8 @@ For plain git hooks, local pre-commit setups, and sandboxing untrusted Lua scrip
240
239
 
241
240
  ## Supported Languages
242
241
 
243
- <!-- <block name="supported-grammar" keep-sorted="asc" keep-sorted-pattern="^- (?P<value>.+)" affects=":pitch"> -->
242
+ <!-- <block name="supported-grammar" keep-sorted="asc" keep-sorted-pattern="^- (?P<value>.+)" affects=":pitch"
243
+ check-lua="scripts/check_language_count.lua" check-lua-pattern="(?m)^- (?P<value>.+)"> -->
244
244
 
245
245
  - Bash (with `.zsh`, `.bashrc`, `.bash_profile`, `.zshrc` and `.envrc` support)
246
246
  - C#
@@ -0,0 +1,40 @@
1
+ # The test fixtures break the rules on purpose.
2
+ ignore = ['tests/testdata/**']
3
+
4
+ # This block runs on a schedule in .github/workflows/trusted-checks.yml as it needs network access and ideally should
5
+ # be avoided in a precommit hook.
6
+ skip-blocks = ['src/validators/check_ai.rs:latest-model']
7
+
8
+ # A block listed in `skip-blocks` is checked only by the scheduled run, so that run must list it too.
9
+ [[block]]
10
+ target = 'blockwatch.toml#/skip-blocks'
11
+ same-as = '.github/workflows/trusted-checks.yml#/jobs/blockwatch/steps/0/with/only_blocks'
12
+ same-as-pattern = '(?P<value>[\w./-]+\.\w+:[\w-]+)'
13
+
14
+ # Users copy the version from the docs, and the pre-commit hooks install the PyPI package that setup.py pins. So each
15
+ # copy must be the version being released. `cargo release` rewrites them with the replacements in release.toml.
16
+ # The SARIF example also holds the version of the SARIF format, so the version only has to be one of its values.
17
+ [[block]]
18
+ target = 'Cargo.toml#/package/version'
19
+ same-as = '''README.md:pre-commit-rev,
20
+ docs/ci.md:pre-commit-rev,
21
+ docs/ci.md:commit-msg-rev,
22
+ docs/cli.md:sarif-example,
23
+ setup.py:blockwatch-cli-version'''
24
+ same-as-mode = 'subset'
25
+
26
+ # The pattern skips the lines that are not a dependency, such as a comment or the items of a `features` list.
27
+ [[block]]
28
+ target = 'Cargo.toml#/dependencies'
29
+ keep-sorted = true
30
+ keep-sorted-pattern = '^(?P<value>[\w-]+) = '
31
+
32
+ # Dependabot updates actions/checkout in the workflows, but not this pin, which dist copies into release.yml.
33
+ # The pattern reads both the pin, `v7`, and each `actions/checkout@v7` in the workflows.
34
+ [[block]]
35
+ target = 'dist-workspace.toml#/dist/github-action-commits/actions~1checkout'
36
+ same-as = '''.github/workflows/codeql.yml,
37
+ .github/workflows/markdownlint.yml,
38
+ .github/workflows/pypi.yml,
39
+ .github/workflows/rust.yml'''
40
+ same-as-pattern = '(?:actions/checkout@|^)(?P<value>v\d+)$'
@@ -15,12 +15,11 @@ It stays fast, because a file without a block tag is not parsed. To check only t
15
15
  To use [pre-commit](https://pre-commit.com), add the hook to `.pre-commit-config.yaml`. pre-commit installs a prebuilt
16
16
  `blockwatch` from PyPI, so you don't need Rust:
17
17
 
18
- <!-- <block name="pre-commit-rev" same-as="Cargo.toml:crate-version" same-as-mode="subset"
19
- same-as-pattern='rev: v(?P<value>\d+\.\d+\.\d+)'> -->
18
+ <!-- <block name="pre-commit-rev" same-as-pattern='rev: v(?P<value>\d+\.\d+\.\d+)'> -->
20
19
 
21
20
  ```yaml
22
21
  - repo: https://github.com/mennanov/blockwatch
23
- rev: v0.11.2 # Use latest release
22
+ rev: v0.12.0 # Use latest release
24
23
  hooks:
25
24
  - id: blockwatch
26
25
  ```
@@ -56,12 +55,11 @@ The `blockwatch` hook runs before the commit message exists, so it cannot see a
56
55
  `blockwatch-commit-msg` hook runs one stage later, at `commit-msg`, and hands the message Git is about to use to
57
56
  [`--suppress-from`](cli.md#suppressing-from-a-file):
58
57
 
59
- <!-- <block name="commit-msg-rev" same-as="Cargo.toml:crate-version" same-as-mode="subset"
60
- same-as-pattern='rev: v(?P<value>\d+\.\d+\.\d+)'> -->
58
+ <!-- <block name="commit-msg-rev" same-as-pattern='rev: v(?P<value>\d+\.\d+\.\d+)'> -->
61
59
 
62
60
  ```yaml
63
61
  - repo: https://github.com/mennanov/blockwatch
64
- rev: v0.11.2 # Use latest release
62
+ rev: v0.12.0 # Use latest release
65
63
  hooks:
66
64
  - id: blockwatch-commit-msg
67
65
  ```
@@ -140,7 +140,7 @@ much — see [Checking Only Changed Blocks](ci.md#checking-only-changed-blocks).
140
140
 
141
141
  ### Supported Diff Input
142
142
 
143
- [//]: # (<block name="diff-input" affects="src/repo_path.rs:diff-target-resolution">)
143
+ <!-- <block name="diff-input" same-as-pattern="`(?P<value>[a-z]/)`"> -->
144
144
 
145
145
  Each diff header points at a file, and `blockwatch` resolves that path against the repository:
146
146
 
@@ -165,7 +165,7 @@ A diff with a file that does not exist in the repository is an error too — but
165
165
  Entries whose extension maps to no language, such as binary assets or lockfiles, contribute no blocks and are passed
166
166
  over, so a diff carrying them alongside source changes still validates normally.
167
167
 
168
- [//]: # (</block>)
168
+ <!-- </block> -->
169
169
 
170
170
  In each case `blockwatch` stops rather than check the wrong file, reporting the diff target at fault. The messages
171
171
  describe the input rather than the command that produced it, since a diff need not come from Git:
@@ -275,6 +275,8 @@ Write globs in single quotes. TOML then takes them as written, backslashes inclu
275
275
 
276
276
  Each key matches a flag:
277
277
 
278
+ <!-- <block name="config-keys" same-as-pattern="^\|\s*`(?P<value>[a-z-]+)`"> -->
279
+
278
280
  | Key | Flag | When the flag is given too |
279
281
  |---------------|----------------|---------------------------------------------------|
280
282
  | `ignore` | `--ignore` | Both lists apply. |
@@ -284,6 +286,8 @@ Each key matches a flag:
284
286
  | `only-blocks` | `--only-block` | The flags' selection replaces the config's. |
285
287
  | `skip-blocks` | `--skip-block` | The flags' selection replaces the config's. |
286
288
 
289
+ <!-- </block> -->
290
+
287
291
  So `blockwatch -e keep-sorted` runs only `keep-sorted`, whatever the config enables or disables. An extra `--ignore`
288
292
  still skips the files the config skips.
289
293
 
@@ -293,6 +297,8 @@ unsupported language, a block address that is not `FILE:BLOCK_NAME`, or both `en
293
297
  cannot turn a setting off without anyone noticing. The error quotes the bad key or value. For an unknown key or a value
294
298
  of the wrong type, it also shows the line and column:
295
299
 
300
+ <!-- <block name="config-key-error" same-as-pattern="`(?P<value>[a-z-]+)`"> -->
301
+
296
302
  ```text
297
303
  Error: invalid config file "blockwatch.toml"
298
304
 
@@ -301,9 +307,11 @@ Caused by:
301
307
  |
302
308
  1 | ignor = ['x']
303
309
  | ^^^^^
304
- unknown field `ignor`, expected one of `ignore`, `extensions`, `enable`, `disable`, `block`
310
+ unknown field `ignor`, expected one of `ignore`, `extensions`, `enable`, `disable`, `only-blocks`, `skip-blocks`, `block`
305
311
  ```
306
312
 
313
+ <!-- </block> -->
314
+
307
315
  Everything else stays out of the file:
308
316
 
309
317
  - **The paths and globs, `--diff`, `--only-changed`, `--suppress` and `--suppress-from`** describe one run, not the
@@ -386,6 +394,7 @@ same-as-pattern = 'os\.environ\["(?P<value>[A-Z_]+)"\]'
386
394
  - **A match can span lines with `(?s)`.** `(?s)^CREATE TABLE users \(.*?^\);` matches the whole statement. Write
387
395
  `.*?`, not `.*`. A `.*` runs to the last `);` in the file, and that still counts as one match.
388
396
  - **A `value` group picks the content**, as in the `*-pattern` attributes. Without one, the content is the whole match.
397
+ A match of a branch without the group stops the run, such as `MODEL = none` for `^MODEL = (?:none|"(?P<value>.+)")`.
389
398
 
390
399
  A virtual block is a block of the file it wraps:
391
400
 
@@ -546,15 +555,15 @@ Nothing else about the run changes: the same violations are found, and the exit
546
555
  "tool": {
547
556
  "driver": {
548
557
  "name": "blockwatch",
549
- "version": "0.11.2",
550
- "semanticVersion": "0.11.2",
558
+ "version": "0.12.0",
559
+ "semanticVersion": "0.12.0",
551
560
  "informationUri": "https://github.com/mennanov/blockwatch",
552
561
  "rules": [
553
562
  {
554
563
  "id": "keep-sorted",
555
564
  "name": "keep-sorted",
556
565
  "shortDescription": { "text": "Requires the lines of a block to stay in order." },
557
- "helpUri": "https://github.com/mennanov/blockwatch/blob/v0.11.2/docs/validators/keep-sorted.md"
566
+ "helpUri": "https://github.com/mennanov/blockwatch/blob/v0.12.0/docs/validators/keep-sorted.md"
558
567
  }
559
568
  ]
560
569
  }
@@ -72,7 +72,7 @@ along with the rest of their file.
72
72
  Controls how a violation is reported. Mirrors
73
73
  [LSP diagnostic severities](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic).
74
74
 
75
- <!-- <block name="severity-levels" affects="src/blocks.rs:block-severity"> -->
75
+ <!-- <block name="severity-levels" same-as-pattern="^\|\s*`(?P<value>[a-z]+)`"> -->
76
76
 
77
77
  | Value | Reported | Exit code |
78
78
  |-------------------|----------|-----------|
@@ -93,7 +93,8 @@ A separator must sit between two digits — `_1`, `1_`, `1__0` and `1_.0` are ty
93
93
  ## Notes
94
94
 
95
95
  - Blank lines and lines the pattern does not match are ignored, not treated as out of order. A line whose match is empty
96
- is ignored as well.
96
+ is ignored as well. So is a line that matches a branch without the `value` group: `^(?:none|(?P<value>\w+))$`
97
+ ignores the line `none`.
97
98
  - Comparison is on the trimmed line, so indentation does not affect ordering. A pattern is applied to the trimmed line
98
99
  too, so `^` and `$` anchor to the entry rather than to the indentation.
99
100
  - An unrecognized `keep-sorted` or `keep-sorted-format` value is a hard error, not a violation.
@@ -36,7 +36,8 @@ ids = [
36
36
  ```
37
37
 
38
38
  Without a `value` group the whole match is compared. Lines that do not match are skipped, as are
39
- lines whose match is empty.
39
+ lines whose match is empty. So is a line that matches a branch without the `value` group: `^(?:none|(?P<value>\w+))$`
40
+ skips the line `none`.
40
41
 
41
42
  ## Notes
42
43
 
@@ -56,8 +56,9 @@ Usually, though, the two places don't hold the same text. Then each block says *
56
56
  ## Pick values with `same-as-pattern`
57
57
 
58
58
  Each block has its own regex. Every match is a value: the `(?P<value>…)` group if there is one, otherwise the whole
59
- match. A line can hold several values. Lines without a match are skipped, and so are empty matches. This lets you
60
- compare blocks written in different formats:
59
+ match. A line can hold several values. Lines without a match are skipped, and so are empty matches. So is a match of a
60
+ branch without the `value` group: `^(?:none|(?P<value>\w+))$` skips the line `none`. This lets you compare blocks
61
+ written in different formats:
61
62
 
62
63
  ```rust
63
64
  // <block same-as="README.md:supported-env-vars" same-as-pattern="BLOCKWATCH_AI_[A-Z_]+">
@@ -36,9 +36,9 @@ exactly = 1
36
36
 
37
37
  # The version is spelled out in the docs as well: the `rev:` of the pre-commit
38
38
  # snippets, and the `blockwatch` version and `helpUri` of the SARIF sample
39
- # output. The `crate-version` block in Cargo.toml lists them all and fails
40
- # a run once any of them goes stale, so they are rewritten here to keep that
41
- # check green. `exactly` aborts the release before publishing to crates.io if a
39
+ # output. The `[[block]]` entry for `Cargo.toml#/package/version` in
40
+ # blockwatch.toml lists them all and fails a run once any of them goes stale,
41
+ # so they are rewritten here to keep that check green. `exactly` aborts the release before publishing to crates.io if a
42
42
  # snippet is ever reworded past these patterns.
43
43
  [[pre-release-replacements]]
44
44
  file = "README.md"
@@ -0,0 +1,19 @@
1
+ -- Checks that the number of languages the README's pitch claims is the number of items in its list of supported
2
+ -- languages. The list's block points at the pitch with `affects`, so the pitch arrives in `ctx.affects`, and the script
3
+ -- needs no file access. `check-lua-pattern` passes one value for each item, so an item that wraps over two lines
4
+ -- counts once.
5
+
6
+ function validate(ctx, content)
7
+ local pitch = ctx.affects and ctx.affects[1]
8
+ if not pitch then
9
+ return "the block must point at the pitch with affects"
10
+ end
11
+ local claimed = pitch.content:match("(%d+) languages")
12
+ if not claimed then
13
+ return "the pitch does not say how many languages are supported"
14
+ end
15
+ if tonumber(claimed) ~= #content then
16
+ return string.format("the pitch claims %s languages, but the list has %d", claimed, #content)
17
+ end
18
+ return nil
19
+ end
@@ -5,8 +5,8 @@ from setuptools import setup
5
5
  setup(
6
6
  name='blockwatch-pre-commit-placeholder',
7
7
  version='0.0.0',
8
- # <block same-as="Cargo.toml:crate-version" same-as-pattern="blockwatch-cli==(?P<value>\d+\.\d+\.\d+)">
9
- install_requires=['blockwatch-cli==0.11.2'],
8
+ # <block name="blockwatch-cli-version" same-as-pattern="blockwatch-cli==(?P<value>\d+\.\d+\.\d+)">
9
+ install_requires=['blockwatch-cli==0.12.0'],
10
10
  # </block>
11
11
  # Without it, setuptools takes the directories under src/ for Python packages.
12
12
  packages=[],
@@ -206,6 +206,10 @@ impl<'source, I: Iterator<Item = Comment>> PartialBlocksIterator<'source, I> {
206
206
  fn take_adjacent_comment(&mut self, comment: &Comment) -> Option<Comment> {
207
207
  let contents = self.contents;
208
208
  self.comments.next_if(|next| {
209
+ #[expect(
210
+ clippy::string_slice,
211
+ reason = "parsers give comment ranges of whole characters"
212
+ )]
209
213
  contents[comment.source_range.end..next.source_range.start]
210
214
  .trim()
211
215
  .is_empty()
@@ -217,6 +221,10 @@ impl<'source, I: Iterator<Item = Comment>> PartialBlocksIterator<'source, I> {
217
221
  fn join_comments(comment: Comment, next: Comment, contents: &str) -> Comment {
218
222
  // The text between the comments is copied from the source, so an offset into the joined text
219
223
  // maps onto the source as it does for a single comment.
224
+ #[expect(
225
+ clippy::string_slice,
226
+ reason = "parsers give comment ranges of whole characters"
227
+ )]
220
228
  let between = &contents[comment.source_range.end..next.source_range.start];
221
229
  Comment {
222
230
  position_range: comment.position_range.start..next.position_range.end,
@@ -315,9 +323,14 @@ impl BlockStart {
315
323
  /// Columns count characters, so the offset is converted by counting the characters it skips
316
324
  /// rather than the bytes: the two differ on any line holding a multi-byte character.
317
325
  fn source_position_at(position_in_comment: usize, comment: &Comment) -> Position {
326
+ #[expect(
327
+ clippy::string_slice,
328
+ reason = "a tag starts at a `<` and ends after a `>`"
329
+ )]
318
330
  let text_before = &comment.comment_text[..position_in_comment];
319
331
  match text_before.rfind('\n') {
320
332
  // Past the comment's first line, so the column is measured from that line's start.
333
+ #[expect(clippy::string_slice, reason = "`line_break` is the index of a `\\n`")]
321
334
  Some(line_break) => Position::new(
322
335
  comment.position_range.start.line + text_before.matches('\n').count(),
323
336
  text_before[line_break + 1..].chars().count() + 1,
@@ -166,6 +166,10 @@ impl Content {
166
166
  /// Returns the content's text. `source` must be the text of the block's file.
167
167
  pub(crate) fn text<'a>(&'a self, source: &'a str) -> &'a str {
168
168
  match &self.text {
169
+ #[expect(
170
+ clippy::string_slice,
171
+ reason = "a comment, a symbol or a match sets `range`"
172
+ )]
169
173
  ContentText::Source(range) => &source[range.clone()],
170
174
  ContentText::Decoded(text) => text,
171
175
  }
@@ -294,7 +298,8 @@ impl Block {
294
298
  #[derive(Clone, Copy, Serialize_repr, EnumString, Debug, PartialEq)]
295
299
  #[strum(ascii_case_insensitive)]
296
300
  #[repr(u8)]
297
- // <block name="block-severity" affects="docs/validators/README.md:severity-levels">
301
+ /* <block name="block-severity" same-as="docs/validators/README.md:severity-levels"
302
+ same-as-format="identifier" same-as-pattern="^(?P<value>[A-Z][a-z]+) = \d"> */
298
303
  pub(crate) enum BlockSeverity {
299
304
  /// The default. A violation at this level makes the run exit non-zero, failing a hook or CI.
300
305
  Error = 1,
@@ -898,7 +903,8 @@ fn virtual_blocks_in_file(
898
903
  }
899
904
 
900
905
  const RECOGNIZED_ATTRIBUTES: &[&str] = &[
901
- // <block keep-sorted>
906
+ /* <block keep-sorted same-as="src/skill.md:validator-reference"
907
+ same-as-pattern='"(?P<value>[a-z-]+)"'> */
902
908
  "affects",
903
909
  "check-ai",
904
910
  "check-ai-pattern",
@@ -909,7 +915,6 @@ const RECOGNIZED_ATTRIBUTES: &[&str] = &[
909
915
  "keep-sorted-format",
910
916
  "keep-sorted-pattern",
911
917
  "keep-unique",
912
- "keep-unique-pattern",
913
918
  "line-count",
914
919
  "line-pattern",
915
920
  "line-pattern-forbidden",
@@ -1001,6 +1006,7 @@ pub(crate) fn parser_for_file_path<'p>(
1001
1006
  let file_name = file_path.file_name()?.to_str()?;
1002
1007
 
1003
1008
  for (i, _) in file_name.match_indices('.').rev() {
1009
+ #[expect(clippy::string_slice, reason = "`i` is the index of a `.`")]
1004
1010
  let extension = &file_name[i + 1..];
1005
1011
  let ext_os = OsString::from(extension);
1006
1012
 
@@ -2815,7 +2821,7 @@ mod supported_languages_tests {
2815
2821
  "/* <block> */\nint main() { return 0; }\n/* </block> */".to_string(),
2816
2822
  ),
2817
2823
  (
2818
- "cc.cpp".to_string(),
2824
+ "cc.cc".to_string(),
2819
2825
  "// <block>\nint main() { return 0; }\n// </block>".to_string(),
2820
2826
  ),
2821
2827
  (
@@ -16,7 +16,7 @@ use crate::violation_address::ViolationAddress;
16
16
  use crate::virtual_blocks::VirtualBlock;
17
17
  use anyhow::Context;
18
18
  use clap::Parser;
19
- use std::collections::HashMap;
19
+ use std::collections::{BTreeMap, HashMap};
20
20
  use std::io::{IsTerminal, Read, Write};
21
21
  use std::path::PathBuf;
22
22
  use std::process::ExitCode;
@@ -147,7 +147,7 @@ fn run_validators(args: &flags::Args) -> anyhow::Result<ExitCode> {
147
147
  /// both leave one behind, and neither says anything about the code being checked.
148
148
  fn apply_suppressions(
149
149
  suppressed_addresses: &[ViolationAddress],
150
- violations: &mut HashMap<RepoPath, Vec<Violation>>,
150
+ violations: &mut BTreeMap<RepoPath, Vec<Violation>>,
151
151
  ) {
152
152
  if suppressed_addresses.is_empty() {
153
153
  return;
@@ -329,7 +329,7 @@ fn read_diff_from_stdin(
329
329
  /// Writes the violations to stderr in the requested format. Returns true if any violation has error
330
330
  /// severity.
331
331
  fn process_violations(
332
- violations: &HashMap<RepoPath, Vec<Violation>>,
332
+ violations: &BTreeMap<RepoPath, Vec<Violation>>,
333
333
  format: flags::OutputFormat,
334
334
  ) -> anyhow::Result<bool> {
335
335
  let has_error_severity = violations.values().flatten().any(|violation| {
@@ -348,9 +348,10 @@ fn process_violations(
348
348
  Ok(has_error_severity)
349
349
  }
350
350
 
351
- /// Writes the violations to stderr as one JSON object of diagnostics grouped by file.
352
- fn write_json_violations(violations: &HashMap<RepoPath, Vec<Violation>>) -> anyhow::Result<()> {
353
- let diagnostics: HashMap<&RepoPath, Vec<_>> = violations
351
+ /// Writes the violations to stderr as one JSON object of diagnostics grouped by file, with the
352
+ /// files sorted by path.
353
+ fn write_json_violations(violations: &BTreeMap<RepoPath, Vec<Violation>>) -> anyhow::Result<()> {
354
+ let diagnostics: BTreeMap<&RepoPath, Vec<_>> = violations
354
355
  .iter()
355
356
  .map(|(file_path, file_violations)| {
356
357
  let file_diagnostics = file_violations
@@ -364,7 +365,7 @@ fn write_json_violations(violations: &HashMap<RepoPath, Vec<Violation>>) -> anyh
364
365
  }
365
366
 
366
367
  /// Writes the violations to stderr as a SARIF log.
367
- fn write_sarif_violations(violations: &HashMap<RepoPath, Vec<Violation>>) -> anyhow::Result<()> {
368
+ fn write_sarif_violations(violations: &BTreeMap<RepoPath, Vec<Violation>>) -> anyhow::Result<()> {
368
369
  write_json(std::io::stderr(), &sarif::SarifLog::new(violations))
369
370
  }
370
371
 
@@ -376,7 +377,8 @@ fn write_sarif_violations(violations: &HashMap<RepoPath, Vec<Violation>>) -> any
376
377
  fn repository_root_path(current_path: PathBuf) -> anyhow::Result<PathBuf> {
377
378
  current_path
378
379
  .ancestors()
379
- // <block affects="src/fs.rs:vcs-metadata-directories">
380
+ /* <block same-as="src/fs.rs:vcs-metadata-directories" same-as-mode="subset"
381
+ same-as-pattern='"(?P<value>\.[a-z]+)"'> */
380
382
  .find(|path| path.join(".git").exists() || path.join(".hg").exists())
381
383
  // </block>
382
384
  .map(|path| path.to_path_buf())
@@ -37,6 +37,10 @@ pub(crate) struct Config {
37
37
  #[derive(Deserialize)]
38
38
  #[serde(deny_unknown_fields)]
39
39
  struct ConfigFile {
40
+ // The error example in the docs also shows `block` and a misspelled key, so the docs only have
41
+ // to hold each of these keys.
42
+ /* <block name="config-keys" same-as="docs/cli.md:config-keys, docs/cli.md:config-key-error"
43
+ same-as-mode="subset" same-as-format="identifier" same-as-pattern="^(?P<value>[a-z_]+):"> */
40
44
  #[serde(default)]
41
45
  ignore: Vec<String>,
42
46
  #[serde(default)]
@@ -49,6 +53,7 @@ struct ConfigFile {
49
53
  only_blocks: Vec<String>,
50
54
  #[serde(default, rename = "skip-blocks")]
51
55
  skip_blocks: Vec<String>,
56
+ // </block>
52
57
  /// The `[[block]]` entries, each with the byte range of its header.
53
58
  #[serde(default)]
54
59
  block: Vec<Spanned<BlockEntry>>,