blockwatch-cli 0.14.1__tar.gz → 0.15.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 (271) hide show
  1. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.github/workflows/pypi.yml +2 -2
  2. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.pre-commit-config.yaml +8 -0
  3. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/AGENTS.md +11 -9
  4. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/CHANGELOG.md +51 -0
  5. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/CLAUDE.md +11 -9
  6. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/Cargo.lock +1 -12
  7. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/Cargo.toml +1 -2
  8. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/PKG-INFO +1 -1
  9. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/README.md +5 -2
  10. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/dist-workspace.toml +1 -1
  11. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/ci.md +2 -2
  12. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/cli.md +104 -4
  13. blockwatch_cli-0.15.0/docs/validators/line-count.md +48 -0
  14. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/setup.py +1 -1
  15. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/blocks.rs +23 -0
  16. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/cli.rs +49 -5
  17. blockwatch_cli-0.15.0/src/explain.rs +69 -0
  18. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/flags.rs +86 -3
  19. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/markdown.rs +306 -154
  20. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/mdx.rs +4 -13
  21. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/lib.rs +1 -0
  22. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/skill.md +8 -1
  23. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/symbols.rs +41 -0
  24. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/target_reference.rs +3 -3
  25. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/validators/affects.rs +217 -108
  26. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/validators/check_ai.rs +222 -95
  27. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/validators/check_lua.rs +266 -139
  28. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/validators/keep_sorted.rs +131 -4
  29. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/validators/keep_unique.rs +141 -2
  30. blockwatch_cli-0.15.0/src/validators/line_count.rs +707 -0
  31. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/validators/line_pattern.rs +129 -4
  32. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/validators/mod.rs +62 -2
  33. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/validators/same_as.rs +476 -79
  34. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/virtual_blocks.rs +1 -10
  35. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/affects.rs +2 -2
  36. blockwatch_cli-0.15.0/tests/explain.rs +221 -0
  37. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/same_as.rs +1 -1
  38. blockwatch_cli-0.15.0/tests/testdata/explain/a.py +16 -0
  39. blockwatch_cli-0.15.0/tests/testdata/explain/check.lua +3 -0
  40. blockwatch_cli-0.14.1/docs/validators/line-count.md +0 -29
  41. blockwatch_cli-0.14.1/src/validators/line_count.rs +0 -403
  42. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.agents/.claude-plugin/plugin.json +0 -0
  43. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.agents/skills/blockwatch/SKILL.md +0 -0
  44. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.claude-plugin/marketplace.json +0 -0
  45. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.github/dependabot.yml +0 -0
  46. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.github/workflows/blockwatch.yml +0 -0
  47. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.github/workflows/codeql.yml +0 -0
  48. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.github/workflows/markdownlint.yml +0 -0
  49. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.github/workflows/release.yml +0 -0
  50. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.github/workflows/rust.yml +0 -0
  51. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.github/workflows/trusted-checks.yml +0 -0
  52. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.gitignore +0 -0
  53. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.markdownlint-cli2.yaml +0 -0
  54. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/.pre-commit-hooks.yaml +0 -0
  55. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/LICENSE +0 -0
  56. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/SECURITY.md +0 -0
  57. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/blockwatch.toml +0 -0
  58. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/clippy.toml +0 -0
  59. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/codecov.yml +0 -0
  60. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/demo.gif +0 -0
  61. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/agents.md +0 -0
  62. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/symbols.md +0 -0
  63. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/validators/README.md +0 -0
  64. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/validators/affects.md +0 -0
  65. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/validators/check-ai.md +0 -0
  66. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/validators/check-lua.md +0 -0
  67. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/validators/keep-sorted.md +0 -0
  68. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/validators/keep-unique.md +0 -0
  69. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/validators/line-pattern.md +0 -0
  70. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/docs/validators/same-as.md +0 -0
  71. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/pypi/pyproject.toml +0 -0
  72. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/pyproject.toml +0 -0
  73. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/release.toml +0 -0
  74. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/scripts/check_language_count.lua +0 -0
  75. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/scripts/check_latest_gpt_nano_model.lua +0 -0
  76. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/block_parser.rs +0 -0
  77. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/config.rs +0 -0
  78. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/diff_parser.rs +0 -0
  79. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/fs.rs +0 -0
  80. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/bash.rs +0 -0
  81. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/c.rs +0 -0
  82. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/c_sharp.rs +0 -0
  83. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/cmake.rs +0 -0
  84. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/cpp.rs +0 -0
  85. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/css.rs +0 -0
  86. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/dart.rs +0 -0
  87. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/dockerfile.rs +0 -0
  88. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/elixir.rs +0 -0
  89. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/go.rs +0 -0
  90. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/graphql.rs +0 -0
  91. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/groovy.rs +0 -0
  92. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/hcl.rs +0 -0
  93. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/html.rs +0 -0
  94. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/java.rs +0 -0
  95. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/javascript.rs +0 -0
  96. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/json.rs +0 -0
  97. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/json.scm +0 -0
  98. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/kotlin.rs +0 -0
  99. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/lean.rs +0 -0
  100. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/lua.rs +0 -0
  101. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/makefile.rs +0 -0
  102. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/mod.rs +0 -0
  103. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/nix.rs +0 -0
  104. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/ocaml.rs +0 -0
  105. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/php.rs +0 -0
  106. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/proto.rs +0 -0
  107. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/python.rs +0 -0
  108. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/ruby.rs +0 -0
  109. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/rust.rs +0 -0
  110. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/scala.rs +0 -0
  111. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/sql.rs +0 -0
  112. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/starlark.rs +0 -0
  113. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/swift.rs +0 -0
  114. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/toml.rs +0 -0
  115. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/tsx.rs +0 -0
  116. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/typescript.rs +0 -0
  117. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/xml.rs +0 -0
  118. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/language_parsers/yaml.rs +0 -0
  119. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/main.rs +0 -0
  120. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/path_arguments.rs +0 -0
  121. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/paths_glob.rs +0 -0
  122. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/repo_path.rs +0 -0
  123. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/report.rs +0 -0
  124. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/sarif.rs +0 -0
  125. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/scan.rs +0 -0
  126. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/settings.rs +0 -0
  127. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/symbol_path.rs +0 -0
  128. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/tag_parser.rs +0 -0
  129. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/src/violation_address.rs +0 -0
  130. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/check_ai.rs +0 -0
  131. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/check_lua.rs +0 -0
  132. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/common/empty.toml +0 -0
  133. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/common/mod.rs +0 -0
  134. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/config.rs +0 -0
  135. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/encoding.rs +0 -0
  136. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/general.rs +0 -0
  137. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/keep_sorted.rs +0 -0
  138. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/keep_unique.rs +0 -0
  139. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/line_count.rs +0 -0
  140. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/line_pattern.rs +0 -0
  141. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/list.rs +0 -0
  142. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/path_arguments.rs +0 -0
  143. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/repository_root.rs +0 -0
  144. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/sarif.rs +0 -0
  145. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/skill.rs +0 -0
  146. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/suppressions.rs +0 -0
  147. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/symbol_references.rs +0 -0
  148. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/cross_file_source.rs +0 -0
  149. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/cross_file_target.md +0 -0
  150. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/custom_ext_source.javascript +0 -0
  151. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/custom_ext_target.javascript +0 -0
  152. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/dangling.md +0 -0
  153. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/missing_symbol.rs +0 -0
  154. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/more_added_than_deleted.py +0 -0
  155. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/multiline_tag_source.rs +0 -0
  156. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/multiline_tag_target.rs +0 -0
  157. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/nested_mapping_source.rs +0 -0
  158. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/nested_mapping_target.yaml +0 -0
  159. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/sibling.md +0 -0
  160. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/symbol_source.rs +0 -0
  161. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/symbol_target.json +0 -0
  162. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/table_source.rs +0 -0
  163. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/table_target.toml +0 -0
  164. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/tag_and_content_source.ts +0 -0
  165. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/tag_and_content_target.ts +0 -0
  166. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/whole_file_source.rs +0 -0
  167. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/affects/whole_file_target.json +0 -0
  168. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_ai/two_blocks.py +0 -0
  169. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/affects.lua +0 -0
  170. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/affects.py +0 -0
  171. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/affects_drift.py +0 -0
  172. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/blocking_io.lua +0 -0
  173. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/blocking_io.py +0 -0
  174. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/echo.lua +0 -0
  175. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/fail.lua +0 -0
  176. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/mode.py +0 -0
  177. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/os.lua +0 -0
  178. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/pathfmt/report_path.lua +0 -0
  179. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/pathfmt/source.py +0 -0
  180. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/pattern.py +0 -0
  181. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/pattern_many_matches.py +0 -0
  182. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/pattern_no_match.py +0 -0
  183. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/success.lua +0 -0
  184. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/check_lua/success_and_failure.py +0 -0
  185. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/config/glob_target/config.toml +0 -0
  186. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/config/glob_target/package.json +0 -0
  187. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/config/glob_target/plugins/blue/package.json +0 -0
  188. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/config/glob_target/plugins/green/package.json +0 -0
  189. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/config/glob_target/plugins/red/package.json +0 -0
  190. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/general/block_selection.py +0 -0
  191. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/general/custom_file_extension.javascript +0 -0
  192. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/general/custom_file_extension.python +0 -0
  193. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/general/disable_enable.py +0 -0
  194. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/general/paths/invalid.py +0 -0
  195. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/general/paths/subdir/nested_invalid.py +0 -0
  196. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/general/paths/valid.py +0 -0
  197. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/general/severity.py +0 -0
  198. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/hidden/.github/workflows/ci.yml +0 -0
  199. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/keep_sorted/orderings.py +0 -0
  200. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/keep_unique/basic_and_pattern.py +0 -0
  201. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/line_count/equality_and_greater_than.py +0 -0
  202. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/line_pattern/forbidden_text.py +0 -0
  203. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/line_pattern/hex_and_word_patterns.py +0 -0
  204. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/list/.hg/.gitkeep +0 -0
  205. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/list/a.py +0 -0
  206. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/list/b.py +0 -0
  207. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/positions/unicode_columns.py +0 -0
  208. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/custom_ext_source.javascript +0 -0
  209. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/custom_ext_target.javascript +0 -0
  210. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/package.json +0 -0
  211. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/paths/alpha/item.txt +0 -0
  212. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/paths/beta/item.txt +0 -0
  213. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/paths_and_block_source.md +0 -0
  214. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/paths_source.md +0 -0
  215. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/sibling.rs +0 -0
  216. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/source.rs +0 -0
  217. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/split_table_source.rs +0 -0
  218. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/split_table_target.toml +0 -0
  219. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/symbol_mismatch_source.rs +0 -0
  220. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/symbol_source.rs +0 -0
  221. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/target.md +0 -0
  222. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/whole_file_mismatch_source.rs +0 -0
  223. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/whole_file_source.rs +0 -0
  224. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/same_as/whole_file_target.json +0 -0
  225. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/sarif/advisory.py +0 -0
  226. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/sarif/clean.py +0 -0
  227. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/sarif/sorted.py +0 -0
  228. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/sarif/unnamed.py +0 -0
  229. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/commit_msg.txt +0 -0
  230. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/commit_msg_gone_a.txt +0 -0
  231. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/commit_msg_gone_b.txt +0 -0
  232. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/commit_msg_lowercase_trailer.txt +0 -0
  233. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/commit_msg_malformed.txt +0 -0
  234. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/commit_msg_with_noise.txt +0 -0
  235. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/dangling.py +0 -0
  236. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/sorted.py +0 -0
  237. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/symbol.py +0 -0
  238. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/symbol_target.json +0 -0
  239. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/two_rules.py +0 -0
  240. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/suppressions/unnamed.py +0 -0
  241. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/escapes_source.rs +0 -0
  242. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/escapes_target.json +0 -0
  243. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/filter_source.rs +0 -0
  244. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/filter_target.yaml +0 -0
  245. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/json_source.rs +0 -0
  246. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/json_target.json +0 -0
  247. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/jsonc_source.rs +0 -0
  248. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/jsonc_target.jsonc +0 -0
  249. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/markdown_source.rs +0 -0
  250. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/markdown_target.markdown +0 -0
  251. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/md_source.rs +0 -0
  252. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/md_target.md +0 -0
  253. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/mdx_source.rs +0 -0
  254. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/mdx_target.mdx +0 -0
  255. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/mixed_list_source.rs +0 -0
  256. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/mixed_list_target.json +0 -0
  257. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/toml_source.rs +0 -0
  258. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/toml_target.toml +0 -0
  259. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/yaml_source.rs +0 -0
  260. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/yaml_target.yaml +0 -0
  261. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/yml_source.rs +0 -0
  262. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/symbol_references/yml_target.yml +0 -0
  263. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/verbosity/affects/pair.py +0 -0
  264. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/verbosity/clean/counted.py +0 -0
  265. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/verbosity/clean/noop.py +0 -0
  266. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/verbosity/clean/notes.txt +0 -0
  267. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/verbosity/clean/sorted.py +0 -0
  268. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/verbosity/diff/two_blocks.py +0 -0
  269. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/verbosity/failing/unsorted.py +0 -0
  270. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/tests/testdata/verbosity/mixed/both_kinds.py +0 -0
  271. {blockwatch_cli-0.14.1 → blockwatch_cli-0.15.0}/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
@@ -43,6 +43,14 @@ repos:
43
43
  types: [ rust ]
44
44
  stages: [ pre-commit ]
45
45
  pass_filenames: false
46
+ - id: cargo-test
47
+ name: cargo test
48
+ description: Run the unit, integration and doc tests.
49
+ entry: cargo test --quiet
50
+ language: system
51
+ types: [ rust ]
52
+ stages: [ pre-commit ]
53
+ pass_filenames: false
46
54
 
47
55
  # Rules and the set of files linted live in `.markdownlint-cli2.yaml`, which the tool picks up
48
56
  # from the repository root. The hook passes only the staged Markdown files, so a commit is judged
@@ -65,9 +65,9 @@ For example:
65
65
  - *Always* handle the variants of an enum with an exhaustive `match`. A `let ... else`, or an `if` that handles one
66
66
  variant and sends the rest to a single branch, quietly gives any new variant whatever that branch does. With a
67
67
  `match`, the compiler makes you decide what to do with each new variant. One exception: a `match` on another
68
- crate's enum can end with `_` when it only asks whether a value is one variant, and no new variant could be that
69
- one. For example, every Markdown heading is `mdast::Node::Heading`, so the other node types can go to `_ => None`.
70
- Say why in a comment next to the `_`.
68
+ crate's enum can end with `_` when it only picks out one or a few variants, and no new variant could be one of
69
+ them. For example, every Markdown heading is `mdast::Node::Heading`, so the other node types can go to
70
+ `_ => None`. Say why in a comment next to the `_`.
71
71
  - *Always* give an item the narrowest visibility that compiles: private, then `pub(super)`, then `pub(crate)`.
72
72
  blockwatch is a program, not a library. Only `run` and `count_rust_blocks` in `lib.rs` are `pub`, for `main.rs` and
73
73
  the fuzz target. The `unreachable_pub` lint makes `cargo clippy -- -D warnings` fail on any other `pub`.
@@ -173,8 +173,9 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
173
173
  from the command line select, and that don't match the settings' ignore globs. `PathArguments::resolve` turns an
174
174
  existing file or directory into a glob first, so `src` selects `src/**`. Pick each file's language by its
175
175
  extension, using the settings' extension mappings too.
176
- 6. For each file, the matching `language_parsers::<lang>` (a tree-sitter grammar) finds the comments. `tag_parser` and
177
- `block_parser` turn the comment text into `Block` values, with their attributes and their byte and line ranges.
176
+ 6. For each file, the matching `language_parsers::<lang>` (usually a tree-sitter grammar) finds the comments.
177
+ `tag_parser` and `block_parser` turn the comment text into `Block` values, with their attributes and their byte and
178
+ line ranges.
178
179
  `VirtualBlock::resolve` finds each virtual block in the file, by its `Locator`, and adds it as a `Block` too. A file
179
180
  without a grammar is read as plain text: it has no tags, only its virtual blocks. A target that does not resolve
180
181
  stops the run. `--only-changed` also parses the file of a virtual block whose entry the diff
@@ -193,7 +194,8 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
193
194
  report goes to stdout. The exit code depends on the error-severity violations that are not suppressed.
194
195
 
195
196
  The `list` subcommand stops after step 6 and writes the blocks it found to stdout as JSON. The `skill` subcommand
196
- prints `src/skill.md` right after step 1.
197
+ prints `src/skill.md` right after step 1. The `explain` subcommand runs like `--only-block` with the file of its
198
+ address as the only path. It then writes that block to stdout as JSON.
197
199
 
198
200
  The main modules:
199
201
 
@@ -223,7 +225,8 @@ The main modules:
223
225
  - `src/repo_path.rs` — `RepoPath`, the one way to write a path relative to the repository root. A diff header
224
226
  (`b/src/main.rs`), a path found in the walk and a `file:name` attribute all become a `RepoPath`. So the same file is
225
227
  always the same map key.
226
- - `src/language_parsers/` — one tree-sitter grammar for each language. Each language has a `parser()` that returns a
228
+ - `src/language_parsers/` — one parser for each language, most of them a tree-sitter grammar. Markdown and MDX use
229
+ the `markdown` crate, and Lean has its own scanner. Each language has a `parser()` that returns a
227
230
  `LanguageParser`, which knows which syntax nodes are comments. `src/block_parser.rs` turns those comments into
228
231
  blocks. `language_parsers()` in `language_parsers/mod.rs` returns `LanguageParsers`: the parser of each extension.
229
232
  Its `parser_for` picks the parser of a file, using the settings' extension mappings too. **To add a language, add a
@@ -237,8 +240,7 @@ The main modules:
237
240
  syntax tree is nested.
238
241
  - YAML walks its syntax tree itself. An alias has a position in a sequence but can't be addressed, and a query can't
239
242
  express that.
240
- - Markdown finds its sections with the `markdown` crate. The `section` nodes of `tree-sitter-md` don't end at a
241
- heading underlined with `---`.
243
+ - Markdown finds its sections with the `markdown` crate, the same parser that finds its comments.
242
244
 
243
245
  Each language's tests list every symbol it finds. When you give a language symbols, also document its paths in
244
246
  `docs/symbols.md` and in the "Symbols" section of the skill, `src/skill.md`. A test in `language_parsers/mod.rs` and the
@@ -12,6 +12,57 @@ 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.15.0] - 2026-10-21
16
+
17
+ ### Changed
18
+
19
+ - In Markdown, a `<!--` is a comment only where HTML is kept as it is: in an HTML block or in raw HTML in a line of
20
+ text. These were read as comments before and are not now:
21
+ - the alt text of an image: `![Logo <!-- x -->](logo.png)`;
22
+ - the title of a link: `[Docs](docs.md "<!-- x -->")`;
23
+ - an escaped `<`: `\<!-- x -->`.
24
+
25
+ In a line of text, `<!-->` is now a whole comment, so a tag after it on that line is not read.
26
+
27
+ ### Fixed
28
+
29
+ - In Markdown, blockwatch skipped every tag after a fenced code block that holds a line like `` ``` `` indented by 4
30
+ spaces or a tab, or `` echo x``` ``. Such a line closed the code block too early, so the rest of the file became
31
+ code. The run gave no error. Now such a line stays in the code block, as CommonMark says
32
+ ([#176](https://github.com/mennanov/blockwatch/issues/176)).
33
+
34
+ ## [0.14.2] - 2026-10-21
35
+
36
+ ### Added
37
+
38
+ - `line-count-pattern` makes `line-count` count only the lines that match the regex. For example,
39
+ `line-count="<=20" line-count-pattern='^"\w+",$'` skips comments and blank lines in a list. See
40
+ [`line-count`](docs/validators/line-count.md).
41
+ - `blockwatch explain FILE:BLOCK_NAME` checks one named block and prints it as JSON: its attributes, the text its
42
+ validators read and where that text is. For example: `blockwatch explain package.json:version`. The output also lists
43
+ the validators the block asks for but the settings turn off (`"disabled"`). Under `"checks"`, it shows what each
44
+ validator saw:
45
+ - `same-as`: each record the pattern matched, with its line, on both sides, and the reason of each mismatch.
46
+ - `keep-sorted` and `keep-unique`: the value each line gives, up to the first line out of order or repeated.
47
+ - `line-pattern`: what each pattern matched on each line, up to the first line that fails.
48
+ - `affects`: whether the block changed, and for each target, whether it changed or why it doesn't resolve.
49
+ - `line-count`: the value each line gives, how many lines count, and the rule they were checked against.
50
+ - `check-lua`: the `content` and `ctx.affects` the script got, and the message it returned.
51
+ - `check-ai`: the model, the prompt it was sent and its answer.
52
+
53
+ `"with_diff"` says whether `--diff` was given. Without it, no block counts as changed. Violations go to stderr, and
54
+ the exit code is the same as for a normal run. See [The `explain` Command](docs/cli.md#the-explain-command).
55
+
56
+ ### Changed
57
+
58
+ - A message of `affects` or `same-as` about a whole-file target shows the path alone, without `file` in front. For
59
+ example: `Block a.py:b at line 1 is modified, but config.json is not`.
60
+
61
+ ### Fixed
62
+
63
+ - A `check-lua` script got no `ctx.affects` entry for a block target that the run didn't check, such as with
64
+ `--only-block` or `--diff --only-changed`. Now `ctx.affects` holds the same targets in every run.
65
+
15
66
  ## [0.14.1] - 2026-10-17
16
67
 
17
68
  ### Changed
@@ -65,9 +65,9 @@ For example:
65
65
  - *Always* handle the variants of an enum with an exhaustive `match`. A `let ... else`, or an `if` that handles one
66
66
  variant and sends the rest to a single branch, quietly gives any new variant whatever that branch does. With a
67
67
  `match`, the compiler makes you decide what to do with each new variant. One exception: a `match` on another
68
- crate's enum can end with `_` when it only asks whether a value is one variant, and no new variant could be that
69
- one. For example, every Markdown heading is `mdast::Node::Heading`, so the other node types can go to `_ => None`.
70
- Say why in a comment next to the `_`.
68
+ crate's enum can end with `_` when it only picks out one or a few variants, and no new variant could be one of
69
+ them. For example, every Markdown heading is `mdast::Node::Heading`, so the other node types can go to
70
+ `_ => None`. Say why in a comment next to the `_`.
71
71
  - *Always* give an item the narrowest visibility that compiles: private, then `pub(super)`, then `pub(crate)`.
72
72
  blockwatch is a program, not a library. Only `run` and `count_rust_blocks` in `lib.rs` are `pub`, for `main.rs` and
73
73
  the fuzz target. The `unreachable_pub` lint makes `cargo clippy -- -D warnings` fail on any other `pub`.
@@ -173,8 +173,9 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
173
173
  from the command line select, and that don't match the settings' ignore globs. `PathArguments::resolve` turns an
174
174
  existing file or directory into a glob first, so `src` selects `src/**`. Pick each file's language by its
175
175
  extension, using the settings' extension mappings too.
176
- 6. For each file, the matching `language_parsers::<lang>` (a tree-sitter grammar) finds the comments. `tag_parser` and
177
- `block_parser` turn the comment text into `Block` values, with their attributes and their byte and line ranges.
176
+ 6. For each file, the matching `language_parsers::<lang>` (usually a tree-sitter grammar) finds the comments.
177
+ `tag_parser` and `block_parser` turn the comment text into `Block` values, with their attributes and their byte and
178
+ line ranges.
178
179
  `VirtualBlock::resolve` finds each virtual block in the file, by its `Locator`, and adds it as a `Block` too. A file
179
180
  without a grammar is read as plain text: it has no tags, only its virtual blocks. A target that does not resolve
180
181
  stops the run. `--only-changed` also parses the file of a virtual block whose entry the diff
@@ -193,7 +194,8 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
193
194
  report goes to stdout. The exit code depends on the error-severity violations that are not suppressed.
194
195
 
195
196
  The `list` subcommand stops after step 6 and writes the blocks it found to stdout as JSON. The `skill` subcommand
196
- prints `src/skill.md` right after step 1.
197
+ prints `src/skill.md` right after step 1. The `explain` subcommand runs like `--only-block` with the file of its
198
+ address as the only path. It then writes that block to stdout as JSON.
197
199
 
198
200
  The main modules:
199
201
 
@@ -223,7 +225,8 @@ The main modules:
223
225
  - `src/repo_path.rs` — `RepoPath`, the one way to write a path relative to the repository root. A diff header
224
226
  (`b/src/main.rs`), a path found in the walk and a `file:name` attribute all become a `RepoPath`. So the same file is
225
227
  always the same map key.
226
- - `src/language_parsers/` — one tree-sitter grammar for each language. Each language has a `parser()` that returns a
228
+ - `src/language_parsers/` — one parser for each language, most of them a tree-sitter grammar. Markdown and MDX use
229
+ the `markdown` crate, and Lean has its own scanner. Each language has a `parser()` that returns a
227
230
  `LanguageParser`, which knows which syntax nodes are comments. `src/block_parser.rs` turns those comments into
228
231
  blocks. `language_parsers()` in `language_parsers/mod.rs` returns `LanguageParsers`: the parser of each extension.
229
232
  Its `parser_for` picks the parser of a file, using the settings' extension mappings too. **To add a language, add a
@@ -237,8 +240,7 @@ The main modules:
237
240
  syntax tree is nested.
238
241
  - YAML walks its syntax tree itself. An alias has a position in a sequence but can't be addressed, and a query can't
239
242
  express that.
240
- - Markdown finds its sections with the `markdown` crate. The `section` nodes of `tree-sitter-md` don't end at a
241
- heading underlined with `---`.
243
+ - Markdown finds its sections with the `markdown` crate, the same parser that finds its comments.
242
244
 
243
245
  Each language's tests list every symbol it finds. When you give a language symbols, also document its paths in
244
246
  `docs/symbols.md` and in the "Symbols" section of the skill, `src/skill.md`. A test in `language_parsers/mod.rs` and the
@@ -274,7 +274,7 @@ checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
274
274
 
275
275
  [[package]]
276
276
  name = "blockwatch"
277
- version = "0.14.1"
277
+ version = "0.15.0"
278
278
  dependencies = [
279
279
  "anyhow",
280
280
  "assert-json-diff",
@@ -327,7 +327,6 @@ dependencies = [
327
327
  "tree-sitter-kotlin-ng",
328
328
  "tree-sitter-lua",
329
329
  "tree-sitter-make",
330
- "tree-sitter-md",
331
330
  "tree-sitter-nix",
332
331
  "tree-sitter-ocaml",
333
332
  "tree-sitter-php",
@@ -2637,16 +2636,6 @@ dependencies = [
2637
2636
  "tree-sitter-language",
2638
2637
  ]
2639
2638
 
2640
- [[package]]
2641
- name = "tree-sitter-md"
2642
- version = "0.5.3"
2643
- source = "registry+https://github.com/rust-lang/crates.io-index"
2644
- checksum = "2efd398be546456c814598ee56c0f51769a77241511b4a58077815d120afa882"
2645
- dependencies = [
2646
- "cc",
2647
- "tree-sitter-language",
2648
- ]
2649
-
2650
2639
  [[package]]
2651
2640
  name = "tree-sitter-nix"
2652
2641
  version = "0.3.0"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "blockwatch"
3
- version = "0.14.1"
3
+ version = "0.15.0"
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"
@@ -62,7 +62,6 @@ tree-sitter-json = "0.24.8"
62
62
  tree-sitter-kotlin-ng = "1.1"
63
63
  tree-sitter-lua = "0.5"
64
64
  tree-sitter-make = "1.1"
65
- tree-sitter-md = "0.5"
66
65
  tree-sitter-nix = "0.3.0"
67
66
  tree-sitter-ocaml = "0.26"
68
67
  tree-sitter-php = "0.25"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blockwatch-cli
3
- Version: 0.14.1
3
+ Version: 0.15.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
@@ -104,7 +104,7 @@ you'd otherwise have to nitpick in code review.
104
104
  | [`keep-sorted`](docs/validators/keep-sorted.md) | Enforces alphabetical or numerical ordering on list items | `keep-sorted`, `keep-sorted-pattern`, `keep-sorted-format` |
105
105
  | [`keep-unique`](docs/validators/keep-unique.md) | Prevents duplicate lines within a block | `keep-unique` |
106
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` |
107
- | [`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` |
108
108
  | [`check-ai`](docs/validators/check-ai.md) | Validates content against natural language rules using an LLM | `check-ai`, `check-ai-pattern` |
109
109
  | [`check-lua`](docs/validators/check-lua.md) | Runs custom validation logic written in Lua | `check-lua`, `check-lua-pattern`, `check-lua-timeout` |
110
110
 
@@ -173,6 +173,9 @@ git diff --patch HEAD | blockwatch --diff --only-changed
173
173
 
174
174
  # Dump all discovered blocks as JSON
175
175
  blockwatch list
176
+
177
+ # Show what each validator of one named block read
178
+ blockwatch explain src/lib.rs:languages
176
179
  ```
177
180
 
178
181
  Rules live in comments, next to the code they describe. Settings that apply to the whole project, such as ignored
@@ -215,7 +218,7 @@ exclusions, and custom extension mappings.
215
218
 
216
219
  ```yaml
217
220
  - repo: https://github.com/mennanov/blockwatch
218
- rev: v0.14.1 # Use latest release
221
+ rev: v0.15.0 # Use latest release
219
222
  hooks:
220
223
  - id: blockwatch
221
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.1 # Use latest release
22
+ rev: v0.15.0 # 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.1 # Use latest release
62
+ rev: v0.15.0 # 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`)
@@ -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.1",
559
- "semanticVersion": "0.14.1",
560
+ "version": "0.15.0",
561
+ "semanticVersion": "0.15.0",
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.1/docs/validators/keep-sorted.md"
568
+ "helpUri": "https://github.com/mennanov/blockwatch/blob/v0.15.0/docs/validators/keep-sorted.md"
567
569
  }
568
570
  ]
569
571
  }
@@ -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
@@ -0,0 +1,48 @@
1
+ # `line-count`
2
+
3
+ Constrains how many lines a block may contain. Flags unbounded growth in a public API surface, or pins a switch to the
4
+ size of the enum it maps.
5
+
6
+ ## Syntax
7
+
8
+ | Attribute | Value | Default |
9
+ |----------------------|--------------------------------------------|--------------------------|
10
+ | `line-count` | `<N`, `<=N`, `==N`, `>=N`, `>N` | — |
11
+ | `line-count-pattern` | regex; a line counts when it gives a value | every line that has text |
12
+
13
+ ## Example
14
+
15
+ ```python
16
+ # <block line-count="<=5">
17
+ "a",
18
+ "b",
19
+ "c"
20
+ # </block>
21
+ ```
22
+
23
+ ## Counting only some lines
24
+
25
+ `line-count-pattern` counts only the lines it matches:
26
+
27
+ ```python
28
+ # <block line-count="<=2" line-count-pattern='^"\w+",$'>
29
+ "a",
30
+ # a comment
31
+ "b",
32
+ # </block>
33
+ ```
34
+
35
+ The block above counts 2: the comment line doesn't match.
36
+
37
+ Lines that do not match are skipped, as are lines whose match is empty. So is a line that matches a branch without the
38
+ `value` group: `^(?:none|(?P<value>\w+))$` skips the line `none`. The pattern runs on the trimmed line, so `^` and `$`
39
+ match at the text, not at the indentation.
40
+
41
+ ## Notes
42
+
43
+ - **Blank lines are not counted.** The block above counts 3.
44
+ - A malformed comparator is a hard error, not a violation.
45
+
46
+ ---
47
+
48
+ ← [Validators](README.md) · [README](../../README.md)
@@ -6,7 +6,7 @@ setup(
6
6
  name='blockwatch-pre-commit-placeholder',
7
7
  version='0.0.0',
8
8
  # <block name="blockwatch-cli-version" same-as-pattern="blockwatch-cli==(?P<value>\d+\.\d+\.\d+)">
9
- install_requires=['blockwatch-cli==0.14.1'],
9
+ install_requires=['blockwatch-cli==0.15.0'],
10
10
  # </block>
11
11
  # Without it, setuptools takes the directories under src/ for Python packages.
12
12
  packages=[],
@@ -184,6 +184,15 @@ impl Content {
184
184
  }
185
185
  }
186
186
 
187
+ /// Describes the content for JSON. `source` must be the text of the block's file.
188
+ pub(crate) fn listing<'a>(&'a self, source: &'a str) -> ContentListing<'a> {
189
+ ContentListing {
190
+ start: self.positions.start,
191
+ end: self.positions.end,
192
+ text: self.text(source),
193
+ }
194
+ }
195
+
187
196
  /// Returns the content's text. `source` must be the text of the block's file.
188
197
  pub(crate) fn text<'a>(&'a self, source: &'a str) -> &'a str {
189
198
  match &self.text {
@@ -424,6 +433,19 @@ pub(crate) struct BlockListing<'a> {
424
433
  }
425
434
  // </block>
426
435
 
436
+ /// The content of a block, as it is written in JSON: its text, and where the block's file writes
437
+ /// it.
438
+ #[derive(Serialize, Debug)]
439
+ pub(crate) struct ContentListing<'a> {
440
+ /// The position of the content's first character.
441
+ start: Position,
442
+ /// The first position after the content.
443
+ end: Position,
444
+ /// The text the validators read. For a virtual block around a quoted or escaped value, it is
445
+ /// the decoded value.
446
+ text: &'a str,
447
+ }
448
+
427
449
  impl<'a> From<&'a BlockWithContext> for BlockListing<'a> {
428
450
  /// Describes the block, with no `checks`.
429
451
  fn from(block_with_context: &'a BlockWithContext) -> Self {
@@ -461,6 +483,7 @@ const RECOGNIZED_ATTRIBUTES: &[&str] = &[
461
483
  "keep-sorted-pattern",
462
484
  "keep-unique",
463
485
  "line-count",
486
+ "line-count-pattern",
464
487
  "line-pattern",
465
488
  "line-pattern-forbidden",
466
489
  "name",