blockwatch-cli 0.14.0__tar.gz → 0.14.2__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 (271) hide show
  1. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.github/workflows/pypi.yml +2 -2
  2. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.github/workflows/rust.yml +8 -0
  3. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.pre-commit-config.yaml +9 -0
  4. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/AGENTS.md +31 -20
  5. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/CHANGELOG.md +50 -0
  6. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/CLAUDE.md +31 -20
  7. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/Cargo.lock +1 -1
  8. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/Cargo.toml +1 -1
  9. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/PKG-INFO +1 -1
  10. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/README.md +6 -2
  11. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/dist-workspace.toml +1 -1
  12. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/ci.md +2 -2
  13. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/cli.md +106 -6
  14. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/validators/README.md +1 -0
  15. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/validators/check-lua.md +4 -3
  16. blockwatch_cli-0.14.2/docs/validators/line-count.md +48 -0
  17. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/setup.py +1 -1
  18. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/block_parser.rs +115 -111
  19. blockwatch_cli-0.14.2/src/blocks.rs +1208 -0
  20. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/cli.rs +134 -96
  21. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/config.rs +51 -24
  22. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/diff_parser.rs +135 -66
  23. blockwatch_cli-0.14.2/src/explain.rs +69 -0
  24. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/flags.rs +119 -26
  25. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/fs.rs +53 -78
  26. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/bash.rs +1 -1
  27. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/c.rs +1 -1
  28. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/c_sharp.rs +1 -1
  29. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/cmake.rs +1 -1
  30. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/cpp.rs +1 -1
  31. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/css.rs +1 -1
  32. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/dart.rs +1 -1
  33. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/dockerfile.rs +10 -9
  34. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/elixir.rs +1 -1
  35. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/go.rs +1 -1
  36. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/graphql.rs +1 -1
  37. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/groovy.rs +1 -1
  38. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/hcl.rs +1 -1
  39. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/html.rs +1 -1
  40. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/java.rs +1 -1
  41. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/javascript.rs +1 -3
  42. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/json.rs +1 -1
  43. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/kotlin.rs +1 -1
  44. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/lean.rs +4 -13
  45. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/lua.rs +1 -1
  46. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/makefile.rs +1 -1
  47. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/markdown.rs +21 -17
  48. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/mdx.rs +17 -26
  49. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/mod.rs +134 -30
  50. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/nix.rs +1 -1
  51. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/ocaml.rs +1 -1
  52. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/php.rs +9 -10
  53. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/proto.rs +1 -1
  54. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/python.rs +1 -1
  55. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/ruby.rs +1 -1
  56. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/rust.rs +1 -1
  57. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/scala.rs +1 -1
  58. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/sql.rs +1 -1
  59. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/starlark.rs +1 -1
  60. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/swift.rs +1 -1
  61. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/toml.rs +7 -7
  62. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/tsx.rs +1 -3
  63. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/typescript.rs +2 -2
  64. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/xml.rs +1 -1
  65. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/yaml.rs +5 -7
  66. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/lib.rs +90 -55
  67. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/path_arguments.rs +5 -3
  68. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/paths_glob.rs +2 -0
  69. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/repo_path.rs +11 -9
  70. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/report.rs +65 -59
  71. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/sarif.rs +49 -76
  72. blockwatch_cli-0.14.0/src/blocks.rs → blockwatch_cli-0.14.2/src/scan.rs +512 -1660
  73. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/settings.rs +131 -104
  74. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/skill.md +8 -1
  75. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/symbol_path.rs +3 -0
  76. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/symbols.rs +50 -12
  77. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/tag_parser.rs +18 -28
  78. blockwatch_cli-0.14.2/src/target_reference.rs +408 -0
  79. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/validators/affects.rs +267 -229
  80. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/validators/check_ai.rs +248 -147
  81. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/validators/check_lua.rs +310 -211
  82. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/validators/keep_sorted.rs +249 -153
  83. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/validators/keep_unique.rs +176 -69
  84. blockwatch_cli-0.14.2/src/validators/line_count.rs +707 -0
  85. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/validators/line_pattern.rs +161 -73
  86. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/validators/mod.rs +394 -667
  87. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/validators/same_as.rs +580 -198
  88. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/violation_address.rs +49 -36
  89. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/virtual_blocks.rs +50 -45
  90. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/affects.rs +2 -2
  91. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/check_ai.rs +2 -2
  92. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/check_lua.rs +4 -4
  93. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/config.rs +1 -1
  94. blockwatch_cli-0.14.2/tests/explain.rs +221 -0
  95. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/keep_sorted.rs +4 -4
  96. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/keep_unique.rs +1 -1
  97. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/line_count.rs +1 -1
  98. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/line_pattern.rs +1 -1
  99. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/repository_root.rs +1 -1
  100. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/same_as.rs +1 -1
  101. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/suppressions.rs +14 -0
  102. blockwatch_cli-0.14.2/tests/testdata/explain/a.py +16 -0
  103. blockwatch_cli-0.14.2/tests/testdata/explain/check.lua +3 -0
  104. blockwatch_cli-0.14.0/docs/validators/line-count.md +0 -29
  105. blockwatch_cli-0.14.0/src/validators/line_count.rs +0 -441
  106. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.agents/.claude-plugin/plugin.json +0 -0
  107. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.agents/skills/blockwatch/SKILL.md +0 -0
  108. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.claude-plugin/marketplace.json +0 -0
  109. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.github/dependabot.yml +0 -0
  110. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.github/workflows/blockwatch.yml +0 -0
  111. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.github/workflows/codeql.yml +0 -0
  112. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.github/workflows/markdownlint.yml +0 -0
  113. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.github/workflows/release.yml +0 -0
  114. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.github/workflows/trusted-checks.yml +0 -0
  115. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.gitignore +0 -0
  116. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.markdownlint-cli2.yaml +0 -0
  117. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/.pre-commit-hooks.yaml +0 -0
  118. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/LICENSE +0 -0
  119. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/SECURITY.md +0 -0
  120. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/blockwatch.toml +0 -0
  121. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/clippy.toml +0 -0
  122. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/codecov.yml +0 -0
  123. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/demo.gif +0 -0
  124. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/agents.md +0 -0
  125. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/symbols.md +0 -0
  126. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/validators/affects.md +0 -0
  127. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/validators/check-ai.md +0 -0
  128. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/validators/keep-sorted.md +0 -0
  129. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/validators/keep-unique.md +0 -0
  130. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/validators/line-pattern.md +0 -0
  131. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/docs/validators/same-as.md +0 -0
  132. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/pypi/pyproject.toml +0 -0
  133. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/pyproject.toml +0 -0
  134. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/release.toml +0 -0
  135. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/scripts/check_language_count.lua +0 -0
  136. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/scripts/check_latest_gpt_nano_model.lua +0 -0
  137. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/language_parsers/json.scm +0 -0
  138. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/src/main.rs +0 -0
  139. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/common/empty.toml +0 -0
  140. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/common/mod.rs +0 -0
  141. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/encoding.rs +0 -0
  142. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/general.rs +0 -0
  143. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/list.rs +0 -0
  144. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/path_arguments.rs +0 -0
  145. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/sarif.rs +0 -0
  146. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/skill.rs +0 -0
  147. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/symbol_references.rs +0 -0
  148. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/cross_file_source.rs +0 -0
  149. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/cross_file_target.md +0 -0
  150. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/custom_ext_source.javascript +0 -0
  151. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/custom_ext_target.javascript +0 -0
  152. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/dangling.md +0 -0
  153. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/missing_symbol.rs +0 -0
  154. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/more_added_than_deleted.py +0 -0
  155. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/multiline_tag_source.rs +0 -0
  156. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/multiline_tag_target.rs +0 -0
  157. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/nested_mapping_source.rs +0 -0
  158. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/nested_mapping_target.yaml +0 -0
  159. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/sibling.md +0 -0
  160. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/symbol_source.rs +0 -0
  161. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/symbol_target.json +0 -0
  162. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/table_source.rs +0 -0
  163. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/table_target.toml +0 -0
  164. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/tag_and_content_source.ts +0 -0
  165. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/tag_and_content_target.ts +0 -0
  166. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/whole_file_source.rs +0 -0
  167. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/affects/whole_file_target.json +0 -0
  168. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_ai/two_blocks.py +0 -0
  169. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/affects.lua +0 -0
  170. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/affects.py +0 -0
  171. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/affects_drift.py +0 -0
  172. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/blocking_io.lua +0 -0
  173. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/blocking_io.py +0 -0
  174. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/echo.lua +0 -0
  175. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/fail.lua +0 -0
  176. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/mode.py +0 -0
  177. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/os.lua +0 -0
  178. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pathfmt/report_path.lua +0 -0
  179. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pathfmt/source.py +0 -0
  180. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pattern.py +0 -0
  181. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pattern_many_matches.py +0 -0
  182. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pattern_no_match.py +0 -0
  183. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/success.lua +0 -0
  184. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/success_and_failure.py +0 -0
  185. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/config.toml +0 -0
  186. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/package.json +0 -0
  187. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/plugins/blue/package.json +0 -0
  188. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/plugins/green/package.json +0 -0
  189. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/plugins/red/package.json +0 -0
  190. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/general/block_selection.py +0 -0
  191. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/general/custom_file_extension.javascript +0 -0
  192. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/general/custom_file_extension.python +0 -0
  193. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/general/disable_enable.py +0 -0
  194. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/general/paths/invalid.py +0 -0
  195. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/general/paths/subdir/nested_invalid.py +0 -0
  196. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/general/paths/valid.py +0 -0
  197. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/general/severity.py +0 -0
  198. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/hidden/.github/workflows/ci.yml +0 -0
  199. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/keep_sorted/orderings.py +0 -0
  200. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/keep_unique/basic_and_pattern.py +0 -0
  201. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/line_count/equality_and_greater_than.py +0 -0
  202. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/line_pattern/forbidden_text.py +0 -0
  203. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/line_pattern/hex_and_word_patterns.py +0 -0
  204. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/list/.hg/.gitkeep +0 -0
  205. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/list/a.py +0 -0
  206. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/list/b.py +0 -0
  207. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/positions/unicode_columns.py +0 -0
  208. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/custom_ext_source.javascript +0 -0
  209. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/custom_ext_target.javascript +0 -0
  210. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/package.json +0 -0
  211. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/paths/alpha/item.txt +0 -0
  212. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/paths/beta/item.txt +0 -0
  213. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/paths_and_block_source.md +0 -0
  214. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/paths_source.md +0 -0
  215. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/sibling.rs +0 -0
  216. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/source.rs +0 -0
  217. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/split_table_source.rs +0 -0
  218. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/split_table_target.toml +0 -0
  219. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/symbol_mismatch_source.rs +0 -0
  220. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/symbol_source.rs +0 -0
  221. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/target.md +0 -0
  222. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/whole_file_mismatch_source.rs +0 -0
  223. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/whole_file_source.rs +0 -0
  224. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/same_as/whole_file_target.json +0 -0
  225. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/sarif/advisory.py +0 -0
  226. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/sarif/clean.py +0 -0
  227. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/sarif/sorted.py +0 -0
  228. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/sarif/unnamed.py +0 -0
  229. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg.txt +0 -0
  230. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_gone_a.txt +0 -0
  231. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_gone_b.txt +0 -0
  232. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_lowercase_trailer.txt +0 -0
  233. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_malformed.txt +0 -0
  234. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_with_noise.txt +0 -0
  235. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/dangling.py +0 -0
  236. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/sorted.py +0 -0
  237. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/symbol.py +0 -0
  238. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/symbol_target.json +0 -0
  239. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/two_rules.py +0 -0
  240. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/unnamed.py +0 -0
  241. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/escapes_source.rs +0 -0
  242. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/escapes_target.json +0 -0
  243. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/filter_source.rs +0 -0
  244. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/filter_target.yaml +0 -0
  245. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/json_source.rs +0 -0
  246. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/json_target.json +0 -0
  247. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/jsonc_source.rs +0 -0
  248. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/jsonc_target.jsonc +0 -0
  249. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/markdown_source.rs +0 -0
  250. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/markdown_target.markdown +0 -0
  251. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/md_source.rs +0 -0
  252. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/md_target.md +0 -0
  253. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/mdx_source.rs +0 -0
  254. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/mdx_target.mdx +0 -0
  255. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/mixed_list_source.rs +0 -0
  256. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/mixed_list_target.json +0 -0
  257. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/toml_source.rs +0 -0
  258. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/toml_target.toml +0 -0
  259. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/yaml_source.rs +0 -0
  260. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/yaml_target.yaml +0 -0
  261. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/yml_source.rs +0 -0
  262. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/yml_target.yml +0 -0
  263. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/affects/pair.py +0 -0
  264. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/clean/counted.py +0 -0
  265. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/clean/noop.py +0 -0
  266. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/clean/notes.txt +0 -0
  267. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/clean/sorted.py +0 -0
  268. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/diff/two_blocks.py +0 -0
  269. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/failing/unsorted.py +0 -0
  270. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/mixed/both_kinds.py +0 -0
  271. {blockwatch_cli-0.14.0 → blockwatch_cli-0.14.2}/tests/verbosity.rs +0 -0
@@ -24,9 +24,9 @@ jobs:
24
24
  target: x86_64-unknown-linux-gnu
25
25
  - runner: ubuntu-24.04-arm
26
26
  target: aarch64-unknown-linux-gnu
27
- - runner: macos-14
27
+ - runner: macos-15
28
28
  target: aarch64-apple-darwin
29
- - runner: macos-14
29
+ - runner: macos-15
30
30
  target: x86_64-apple-darwin
31
31
  - runner: windows-latest
32
32
  target: x86_64-pc-windows-msvc
@@ -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
@@ -187,16 +193,19 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
187
193
  report goes to stdout. The exit code depends on the error-severity violations that are not suppressed.
188
194
 
189
195
  The `list` subcommand stops after step 6 and writes the blocks it found to stdout as JSON. The `skill` subcommand
190
- prints `src/skill.md` right after step 1.
196
+ prints `src/skill.md` right after step 1. The `explain` subcommand runs like `--only-block` with the file of its
197
+ address as the only path. It then writes that block to stdout as JSON.
191
198
 
192
199
  The main modules:
193
200
 
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`.
201
+ - `src/fs.rs` — the `FileSystem` trait and its real implementation, and `PathFilter`, which decides whether a run
202
+ reads a path: it must match an allow glob and no ignore glob. Tests swap in `FakeFileSystem` from `fs::test_utils`.
203
+ They build a real `PathFilter` with `PathFilter::allow_all()`, `allow_only` or `with_ignored_paths`.
196
204
  - `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.
205
+ the validated and merged result. `RawSettings::parse` is the only place the rules for a setting live, so the flags
206
+ and the config file can't drift apart. It returns the parsed values, so nothing is parsed twice. Its errors quote the
207
+ bad value but don't show a line and column. Tracking where each value is written would cost more code than it saves
208
+ the reader.
200
209
  - `src/config.rs` — reads the config file into a `RawSettings` and the virtual blocks, using `toml_edit`'s serde
201
210
  support. Only the errors that `toml_edit` raises itself, such as an unknown key, show a line and column. An error
202
211
  about a `[[block]]` entry shows the line of its header, which `serde_spanned` gives.
@@ -217,8 +226,9 @@ The main modules:
217
226
  always the same map key.
218
227
  - `src/language_parsers/` — one tree-sitter grammar for each language. Each language has a `parser()` that returns a
219
228
  `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.**
229
+ blocks. `language_parsers()` in `language_parsers/mod.rs` returns `LanguageParsers`: the parser of each extension.
230
+ Its `parser_for` picks the parser of a file, using the settings' extension mappings too. **To add a language, add a
231
+ module here and register it in that function.**
222
232
 
223
233
  Some languages also have symbols. Such a language has a `SymbolsParser`, which its `parser()` passes to
224
234
  `.with_symbols(...)`:
@@ -241,12 +251,13 @@ The main modules:
241
251
  a file with a syntax error, instead of resolving a path through tree-sitter's error recovery. It doesn't check the
242
252
  query itself.
243
253
  - `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.
254
+ `line_count`, `line_pattern`, `same_as`). Each file exports a `detect` function. `Validator::detect` in
255
+ `validators/mod.rs` calls them from one `match`.
256
+ - `src/diff_parser.rs` — wraps `unidiff` to produce `LineChange`s, kept in `LineChanges` for a file and in
257
+ `DiffChanges` for the whole diff. `LineChanges::touches` decides whether they touch a range of the file, such as a
258
+ block's start tag or a symbol's definition. A block's content has its own check in `blocks.rs`, because a deletion
259
+ right where the content starts removes content. The results end up in `is_content_modified` and
260
+ `is_start_tag_modified` on `BlockWithContext`, which decide which blocks count as changed.
250
261
 
251
262
  A block counts as changed only when its content or its start tag overlaps a `LineChange`. With
252
263
  `--diff --only-changed`, only changed blocks are validated. With `--diff` alone, every block is validated, but rules
@@ -12,6 +12,56 @@ 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.2] - 2026-10-21
16
+
17
+ ### Added
18
+
19
+ - `line-count-pattern` makes `line-count` count only the lines that match the regex. For example,
20
+ `line-count="<=20" line-count-pattern='^"\w+",$'` skips comments and blank lines in a list. See
21
+ [`line-count`](docs/validators/line-count.md).
22
+ - `blockwatch explain FILE:BLOCK_NAME` checks one named block and prints it as JSON: its attributes, the text its
23
+ validators read and where that text is. For example: `blockwatch explain package.json:version`. The output also lists
24
+ the validators the block asks for but the settings turn off (`"disabled"`). Under `"checks"`, it shows what each
25
+ validator saw:
26
+ - `same-as`: each record the pattern matched, with its line, on both sides, and the reason of each mismatch.
27
+ - `keep-sorted` and `keep-unique`: the value each line gives, up to the first line out of order or repeated.
28
+ - `line-pattern`: what each pattern matched on each line, up to the first line that fails.
29
+ - `affects`: whether the block changed, and for each target, whether it changed or why it doesn't resolve.
30
+ - `line-count`: the value each line gives, how many lines count, and the rule they were checked against.
31
+ - `check-lua`: the `content` and `ctx.affects` the script got, and the message it returned.
32
+ - `check-ai`: the model, the prompt it was sent and its answer.
33
+
34
+ `"with_diff"` says whether `--diff` was given. Without it, no block counts as changed. Violations go to stderr, and
35
+ the exit code is the same as for a normal run. See [The `explain` Command](docs/cli.md#the-explain-command).
36
+
37
+ ### Changed
38
+
39
+ - A message of `affects` or `same-as` about a whole-file target shows the path alone, without `file` in front. For
40
+ example: `Block a.py:b at line 1 is modified, but config.json is not`.
41
+
42
+ ### Fixed
43
+
44
+ - A `check-lua` script got no `ctx.affects` entry for a block target that the run didn't check, such as with
45
+ `--only-block` or `--diff --only-changed`. Now `ctx.affects` holds the same targets in every run.
46
+
47
+ ## [0.14.1] - 2026-10-17
48
+
49
+ ### Changed
50
+
51
+ - The error for a wrong `keep-sorted`, `keep-sorted-format`, `same-as-mode`, `same-as-format` or `BLOCKWATCH_LUA_MODE`
52
+ value now has one form, such as `invalid keep-sorted "up" (expected asc or desc)`. It lists the valid values.
53
+ `keep-sorted-format` did not list them before.
54
+ - `same-as-mode`, `same-as-format` and `BLOCKWATCH_LUA_MODE` ignore upper and lower case, so `Numeric` and `Safe` work
55
+ like `numeric` and `safe`. `keep-sorted` and `keep-sorted-format` already did.
56
+ - A message about a block of `keep-sorted`, `keep-unique`, `line-count`, `line-pattern`, `check-ai` or `check-lua` now
57
+ says `at line 3` where it said `defined at line 3`, as the messages of `affects` and `same-as` always did. For
58
+ example: `Block src/a.rs:limits at line 3 has an out-of-order line 5 (asc)`.
59
+ - Every error now starts with a lowercase letter and has no period at the end. Before, about 30 started with a capital
60
+ letter or ended with a period. For example: `failed to read file "a.py"`, `could not find the repository root
61
+ directory` and `malformed block tag at line 1, column 4. If this is not meant as a tag, put it in backticks`. An
62
+ error that gives advice in a second sentence keeps that sentence. An error that starts with a name, such as `API` or
63
+ `Lua`, keeps its capital letter.
64
+
15
65
  ## [0.14.0] - 2026-10-16
16
66
 
17
67
  ### 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
@@ -187,16 +193,19 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
187
193
  report goes to stdout. The exit code depends on the error-severity violations that are not suppressed.
188
194
 
189
195
  The `list` subcommand stops after step 6 and writes the blocks it found to stdout as JSON. The `skill` subcommand
190
- prints `src/skill.md` right after step 1.
196
+ prints `src/skill.md` right after step 1. The `explain` subcommand runs like `--only-block` with the file of its
197
+ address as the only path. It then writes that block to stdout as JSON.
191
198
 
192
199
  The main modules:
193
200
 
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`.
201
+ - `src/fs.rs` — the `FileSystem` trait and its real implementation, and `PathFilter`, which decides whether a run
202
+ reads a path: it must match an allow glob and no ignore glob. Tests swap in `FakeFileSystem` from `fs::test_utils`.
203
+ They build a real `PathFilter` with `PathFilter::allow_all()`, `allow_only` or `with_ignored_paths`.
196
204
  - `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.
205
+ the validated and merged result. `RawSettings::parse` is the only place the rules for a setting live, so the flags
206
+ and the config file can't drift apart. It returns the parsed values, so nothing is parsed twice. Its errors quote the
207
+ bad value but don't show a line and column. Tracking where each value is written would cost more code than it saves
208
+ the reader.
200
209
  - `src/config.rs` — reads the config file into a `RawSettings` and the virtual blocks, using `toml_edit`'s serde
201
210
  support. Only the errors that `toml_edit` raises itself, such as an unknown key, show a line and column. An error
202
211
  about a `[[block]]` entry shows the line of its header, which `serde_spanned` gives.
@@ -217,8 +226,9 @@ The main modules:
217
226
  always the same map key.
218
227
  - `src/language_parsers/` — one tree-sitter grammar for each language. Each language has a `parser()` that returns a
219
228
  `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.**
229
+ blocks. `language_parsers()` in `language_parsers/mod.rs` returns `LanguageParsers`: the parser of each extension.
230
+ Its `parser_for` picks the parser of a file, using the settings' extension mappings too. **To add a language, add a
231
+ module here and register it in that function.**
222
232
 
223
233
  Some languages also have symbols. Such a language has a `SymbolsParser`, which its `parser()` passes to
224
234
  `.with_symbols(...)`:
@@ -241,12 +251,13 @@ The main modules:
241
251
  a file with a syntax error, instead of resolving a path through tree-sitter's error recovery. It doesn't check the
242
252
  query itself.
243
253
  - `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.
254
+ `line_count`, `line_pattern`, `same_as`). Each file exports a `detect` function. `Validator::detect` in
255
+ `validators/mod.rs` calls them from one `match`.
256
+ - `src/diff_parser.rs` — wraps `unidiff` to produce `LineChange`s, kept in `LineChanges` for a file and in
257
+ `DiffChanges` for the whole diff. `LineChanges::touches` decides whether they touch a range of the file, such as a
258
+ block's start tag or a symbol's definition. A block's content has its own check in `blocks.rs`, because a deletion
259
+ right where the content starts removes content. The results end up in `is_content_modified` and
260
+ `is_start_tag_modified` on `BlockWithContext`, which decide which blocks count as changed.
250
261
 
251
262
  A block counts as changed only when its content or its start tag overlaps a `LineChange`. With
252
263
  `--diff --only-changed`, only changed blocks are validated. With `--diff` alone, every block is validated, but rules
@@ -274,7 +274,7 @@ checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
274
274
 
275
275
  [[package]]
276
276
  name = "blockwatch"
277
- version = "0.14.0"
277
+ version = "0.14.2"
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.14.0"
3
+ version = "0.14.2"
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.14.0
3
+ Version: 0.14.2
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 |
@@ -103,7 +104,7 @@ you'd otherwise have to nitpick in code review.
103
104
  | [`keep-sorted`](docs/validators/keep-sorted.md) | Enforces alphabetical or numerical ordering on list items | `keep-sorted`, `keep-sorted-pattern`, `keep-sorted-format` |
104
105
  | [`keep-unique`](docs/validators/keep-unique.md) | Prevents duplicate lines within a block | `keep-unique` |
105
106
  | [`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
+ | [`line-count`](docs/validators/line-count.md) | Enforces lower or upper bounds on the number of lines in a block | `line-count`, `line-count-pattern` |
107
108
  | [`check-ai`](docs/validators/check-ai.md) | Validates content against natural language rules using an LLM | `check-ai`, `check-ai-pattern` |
108
109
  | [`check-lua`](docs/validators/check-lua.md) | Runs custom validation logic written in Lua | `check-lua`, `check-lua-pattern`, `check-lua-timeout` |
109
110
 
@@ -172,6 +173,9 @@ git diff --patch HEAD | blockwatch --diff --only-changed
172
173
 
173
174
  # Dump all discovered blocks as JSON
174
175
  blockwatch list
176
+
177
+ # Show what each validator of one named block read
178
+ blockwatch explain src/lib.rs:languages
175
179
  ```
176
180
 
177
181
  Rules live in comments, next to the code they describe. Settings that apply to the whole project, such as ignored
@@ -214,7 +218,7 @@ exclusions, and custom extension mappings.
214
218
 
215
219
  ```yaml
216
220
  - repo: https://github.com/mennanov/blockwatch
217
- rev: v0.14.0 # Use latest release
221
+ rev: v0.14.2 # Use latest release
218
222
  hooks:
219
223
  - id: blockwatch
220
224
  ```
@@ -27,7 +27,7 @@ publish-jobs = ["homebrew"]
27
27
  global = "ubuntu-latest"
28
28
 
29
29
  [dist.github-custom-runners.aarch64-apple-darwin]
30
- runner = "macos-14"
30
+ runner = "macos-15"
31
31
 
32
32
  [dist.github-custom-runners.x86_64-unknown-linux-gnu]
33
33
  runner = "ubuntu-latest"
@@ -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.14.0 # Use latest release
22
+ rev: v0.14.2 # 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.14.0 # Use latest release
62
+ rev: v0.14.2 # Use latest release
63
63
  hooks:
64
64
  - id: blockwatch-commit-msg
65
65
  ```
@@ -14,6 +14,8 @@ For command-line flag documentation directly in your terminal, run `blockwatch -
14
14
  every block in the repository.
15
15
  - **List Blocks**: `blockwatch list` outputs a JSON report of all discovered blocks. It takes `--diff`,
16
16
  `--only-changed`, `--config`, `-E` and `--ignore`.
17
+ - **Explain a Block**: `blockwatch explain FILE:BLOCK_NAME` checks one named block and prints what each of its
18
+ validators read, as JSON. See [The `explain` Command](#the-explain-command).
17
19
  - **Print the Agent Skill**: `blockwatch skill` prints the [skill for AI agents](#the-skill-command), as a `SKILL.md`
18
20
  - **Config File**: `blockwatch --config FILE` reads the project's settings from FILE instead of `blockwatch.toml`
19
21
  - **Custom Extensions**: Map custom file extensions: `blockwatch -E ino=cpp` (config key: `extensions`)
@@ -172,7 +174,7 @@ describe the input rather than the command that produced it, since a diff need n
172
174
 
173
175
  ```console
174
176
  $ git diff --no-prefix | blockwatch --diff
175
- Error: diff target "rules.py" has no recognized path prefix (a/, b/, i/, w/, c/, o/).
177
+ Error: diff target "rules.py" has no recognized path prefix (a/, b/, i/, w/, c/, o/)
176
178
  ```
177
179
 
178
180
  With Git, that means `--no-prefix`, `diff.noprefix`, or a custom `diff.srcPrefix` / `diff.dstPrefix`. To produce
@@ -555,15 +557,15 @@ Nothing else about the run changes: the same violations are found, and the exit
555
557
  "tool": {
556
558
  "driver": {
557
559
  "name": "blockwatch",
558
- "version": "0.14.0",
559
- "semanticVersion": "0.14.0",
560
+ "version": "0.14.2",
561
+ "semanticVersion": "0.14.2",
560
562
  "informationUri": "https://github.com/mennanov/blockwatch",
561
563
  "rules": [
562
564
  {
563
565
  "id": "keep-sorted",
564
566
  "name": "keep-sorted",
565
567
  "shortDescription": { "text": "Requires the lines of a block to stay in order." },
566
- "helpUri": "https://github.com/mennanov/blockwatch/blob/v0.14.0/docs/validators/keep-sorted.md"
568
+ "helpUri": "https://github.com/mennanov/blockwatch/blob/v0.14.2/docs/validators/keep-sorted.md"
567
569
  }
568
570
  ]
569
571
  }
@@ -574,7 +576,7 @@ Nothing else about the run changes: the same violations are found, and the exit
574
576
  "ruleId": "keep-sorted",
575
577
  "ruleIndex": 0,
576
578
  "level": "error",
577
- "message": { "text": "Block fruits.py:fruits defined at line 2 has an out-of-order line 4 (asc)" },
579
+ "message": { "text": "Block fruits.py:fruits at line 2 has an out-of-order line 4 (asc)" },
578
580
  "locations": [
579
581
  {
580
582
  "physicalLocation": {
@@ -617,7 +619,7 @@ What to expect from the log:
617
619
  - **Lines and columns are 1-based, with an exclusive end column.** A column counts characters, which the run declares as
618
620
  `"columnKind": "unicodeCodePoints"` so that a consumer does not count UTF-16 code units instead.
619
621
 
620
- `--format` cannot be combined with the `list` subcommand, which reports blocks rather than violations. To upload a log
622
+ `--format` cannot be combined with the `list` and `explain` subcommands, which write JSON of their own. To upload a log
621
623
  to GitHub code scanning, see [CI Integration](ci.md#github-code-scanning).
622
624
 
623
625
  ## The `list` Command
@@ -701,6 +703,104 @@ block's attributes are sorted by name; the files themselves are not ordered, and
701
703
  emit them differently. Sort by key downstream if you need to diff one run against another — or use
702
704
  [`--verbosity full`](#full-reports), which does order its files.
703
705
 
706
+ ## The `explain` Command
707
+
708
+ `blockwatch explain FILE:BLOCK_NAME` checks one named block and prints what each of its validators read, as JSON. Run it
709
+ after you write a rule, to see that the rule matches what you meant. A rule can be wrong and still pass. For example, a
710
+ `same-as-pattern` can take the same wrong part of each line on both sides.
711
+
712
+ ```shell
713
+ blockwatch explain src/colors.py:colors
714
+ blockwatch explain package.json:version # a block from blockwatch.toml
715
+ git diff --patch | blockwatch explain --diff src/colors.py:colors # affects needs the diff
716
+ ```
717
+
718
+ - It takes one address, as `--only-block` does. A block without a `name` can't be explained, so give it one first.
719
+ - It takes `--diff`, `--config`, `-E` and `--ignore`, and rejects the other flags.
720
+ - `only-blocks` and `skip-blocks` in `blockwatch.toml` play no part, because the address already picks the block.
721
+ `enable` and `disable` still apply.
722
+ - The explanation goes to stdout. Violations go to stderr, and the [exit code](#exit-codes) is the one a normal run
723
+ gives. `check-ai` calls the API, as in a normal run.
724
+
725
+ ### Explain Output Example
726
+
727
+ For this block:
728
+
729
+ ```python
730
+ COLORS = [
731
+ # <block name="colors" keep-sorted="asc" keep-sorted-pattern='"(?P<value>\w+)"'>
732
+ "blue",
733
+ "red",
734
+ # </block>
735
+ ]
736
+ ```
737
+
738
+ `blockwatch explain src/colors.py:colors` prints:
739
+
740
+ [//]: # (<block name="explain-output-example">)
741
+
742
+ ```json
743
+ {
744
+ "block": {
745
+ "attributes": {
746
+ "keep-sorted": "asc",
747
+ "keep-sorted-pattern": "\"(?P<value>\\w+)\"",
748
+ "name": "colors"
749
+ },
750
+ "column": 7,
751
+ "is_content_modified": false,
752
+ "line": 2,
753
+ "name": "colors"
754
+ },
755
+ "checks": {
756
+ "keep-sorted": {
757
+ "format": "lexicographic",
758
+ "lines": [
759
+ { "line": 3, "text": "\"blue\",", "value": "blue" },
760
+ { "line": 4, "text": "\"red\",", "value": "red" }
761
+ ],
762
+ "order": "asc"
763
+ }
764
+ },
765
+ "content": {
766
+ "start": { "line": 2, "character": 85 },
767
+ "end": { "line": 5, "character": 5 },
768
+ "text": "\n \"blue\",\n \"red\",\n "
769
+ },
770
+ "disabled": [],
771
+ "file": "src/colors.py",
772
+ "with_diff": false
773
+ }
774
+ ```
775
+
776
+ [//]: # (</block>)
777
+
778
+ | Key | What it holds |
779
+ |-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
780
+ | `block` | The block, as [`list`](#the-list-command) writes it. |
781
+ | `checks` | What each validator read, by validator. See [What Each Validator Shows](#what-each-validator-shows). |
782
+ | `content` | The text the validators read. `start` is its first character, and `end` is the first character after it. The column is `character`, as in the `range` of a violation. For a [virtual block](#blocks-in-the-config-file) around a quoted or escaped value, `text` is the decoded value. |
783
+ | `disabled` | The validators that the block asks for, but the settings turn off. They have no entry in `checks`. |
784
+ | `file` | The block's file. |
785
+ | `with_diff` | Whether `--diff` was given. Without it, no block counts as changed, so `affects` can't fire. |
786
+
787
+ ### What Each Validator Shows
788
+
789
+ A trace stops where its validator stops, such as at the first line out of order. A `line` is a line of the block's
790
+ file, and `text` is that line trimmed, as the validator compares it. A key that holds what a violation reports has the
791
+ name the violation's `data` gives it, such as `reason`.
792
+
793
+ | Validator | What it shows |
794
+ |----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
795
+ | `same-as` | `records`: each match of `same-as-pattern` in the block, with its `line`, the text the regex matched (`match`), and each named group after `same-as-format` (`groups`). `targets`: the `records` of each target, and the `reason` of its violation, or `null` when the two agree. `paths`: with `same-as-paths`, the `glob`, the paths it `matched`, their `records` and the `reason`. |
796
+ | `keep-sorted` | `lines`: each line read, with the `value` it compares, or `null` when `keep-sorted-pattern` skips the line. `format` and `order`. |
797
+ | `keep-unique` | `lines`, as for `keep-sorted`. A repeated value has `duplicate_of`: the line where it first appeared. |
798
+ | `line-pattern` | `lines`: each line read, with the text `line-pattern` matched (`match`). A line that fails has `fails`: the check it fails. `forbidden_match` is the text `line-pattern-forbidden` found. |
799
+ | `line-count` | `lines`, as for `keep-sorted`. A line counts when its `value` isn't `null`. The number of lines counted (`actual`), and the rule (`op` and `expected`). |
800
+ | `affects` | Whether the diff changed the block (`block_changed`). For each target: whether the diff `changed` it, or the `reason` it doesn't resolve. |
801
+ | `check-lua` | The `content` and the `affects` the script got, and the message it returned (`lua_error`), or `null` when it passed. `affects` is `ctx.affects` without the content of each target. |
802
+ | `check-ai` | The `model`, the `prompt` sent and the `response`. Both are `null` when `check-ai-pattern` matched nothing, so no request was sent. |
803
+
704
804
  ## Run Reports
705
805
 
706
806
  By default `blockwatch` prints nothing when a run succeeds. That makes a check that passed look exactly like a check
@@ -26,6 +26,7 @@ between two references is fine.
26
26
  <!-- <block name="validators-index"
27
27
  affects="src/skill.md#/blockwatch/Annotating a new project/Where blocks add value (catalog)"
28
28
  same-as="src/validators/mod.rs:validator-registry, README.md:available-validators"
29
+ same-as-format="identifier"
29
30
  same-as-pattern='\[`(?P<value>[a-z-]+)`\]\('> -->
30
31
 
31
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,