blockwatch-cli 0.14.1__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.1 → blockwatch_cli-0.14.2}/.github/workflows/pypi.yml +2 -2
  2. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/AGENTS.md +2 -1
  3. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/CHANGELOG.md +32 -0
  4. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/CLAUDE.md +2 -1
  5. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/Cargo.lock +1 -1
  6. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/Cargo.toml +1 -1
  7. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/PKG-INFO +1 -1
  8. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/README.md +5 -2
  9. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/dist-workspace.toml +1 -1
  10. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/ci.md +2 -2
  11. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/cli.md +104 -4
  12. blockwatch_cli-0.14.2/docs/validators/line-count.md +48 -0
  13. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/setup.py +1 -1
  14. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/blocks.rs +23 -0
  15. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/cli.rs +49 -5
  16. blockwatch_cli-0.14.2/src/explain.rs +69 -0
  17. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/flags.rs +86 -3
  18. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/lib.rs +1 -0
  19. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/skill.md +8 -1
  20. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/symbols.rs +41 -0
  21. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/target_reference.rs +3 -3
  22. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/validators/affects.rs +217 -108
  23. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/validators/check_ai.rs +222 -95
  24. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/validators/check_lua.rs +266 -139
  25. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/validators/keep_sorted.rs +131 -4
  26. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/validators/keep_unique.rs +141 -2
  27. blockwatch_cli-0.14.2/src/validators/line_count.rs +707 -0
  28. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/validators/line_pattern.rs +129 -4
  29. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/validators/mod.rs +62 -2
  30. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/validators/same_as.rs +476 -79
  31. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/virtual_blocks.rs +1 -10
  32. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/affects.rs +2 -2
  33. blockwatch_cli-0.14.2/tests/explain.rs +221 -0
  34. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/same_as.rs +1 -1
  35. blockwatch_cli-0.14.2/tests/testdata/explain/a.py +16 -0
  36. blockwatch_cli-0.14.2/tests/testdata/explain/check.lua +3 -0
  37. blockwatch_cli-0.14.1/docs/validators/line-count.md +0 -29
  38. blockwatch_cli-0.14.1/src/validators/line_count.rs +0 -403
  39. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.agents/.claude-plugin/plugin.json +0 -0
  40. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.agents/skills/blockwatch/SKILL.md +0 -0
  41. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.claude-plugin/marketplace.json +0 -0
  42. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.github/dependabot.yml +0 -0
  43. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.github/workflows/blockwatch.yml +0 -0
  44. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.github/workflows/codeql.yml +0 -0
  45. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.github/workflows/markdownlint.yml +0 -0
  46. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.github/workflows/release.yml +0 -0
  47. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.github/workflows/rust.yml +0 -0
  48. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.github/workflows/trusted-checks.yml +0 -0
  49. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.gitignore +0 -0
  50. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.markdownlint-cli2.yaml +0 -0
  51. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.pre-commit-config.yaml +0 -0
  52. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/.pre-commit-hooks.yaml +0 -0
  53. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/LICENSE +0 -0
  54. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/SECURITY.md +0 -0
  55. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/blockwatch.toml +0 -0
  56. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/clippy.toml +0 -0
  57. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/codecov.yml +0 -0
  58. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/demo.gif +0 -0
  59. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/agents.md +0 -0
  60. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/symbols.md +0 -0
  61. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/validators/README.md +0 -0
  62. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/validators/affects.md +0 -0
  63. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/validators/check-ai.md +0 -0
  64. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/validators/check-lua.md +0 -0
  65. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/validators/keep-sorted.md +0 -0
  66. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/validators/keep-unique.md +0 -0
  67. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/validators/line-pattern.md +0 -0
  68. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/docs/validators/same-as.md +0 -0
  69. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/pypi/pyproject.toml +0 -0
  70. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/pyproject.toml +0 -0
  71. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/release.toml +0 -0
  72. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/scripts/check_language_count.lua +0 -0
  73. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/scripts/check_latest_gpt_nano_model.lua +0 -0
  74. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/block_parser.rs +0 -0
  75. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/config.rs +0 -0
  76. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/diff_parser.rs +0 -0
  77. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/fs.rs +0 -0
  78. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/bash.rs +0 -0
  79. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/c.rs +0 -0
  80. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/c_sharp.rs +0 -0
  81. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/cmake.rs +0 -0
  82. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/cpp.rs +0 -0
  83. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/css.rs +0 -0
  84. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/dart.rs +0 -0
  85. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/dockerfile.rs +0 -0
  86. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/elixir.rs +0 -0
  87. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/go.rs +0 -0
  88. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/graphql.rs +0 -0
  89. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/groovy.rs +0 -0
  90. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/hcl.rs +0 -0
  91. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/html.rs +0 -0
  92. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/java.rs +0 -0
  93. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/javascript.rs +0 -0
  94. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/json.rs +0 -0
  95. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/json.scm +0 -0
  96. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/kotlin.rs +0 -0
  97. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/lean.rs +0 -0
  98. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/lua.rs +0 -0
  99. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/makefile.rs +0 -0
  100. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/markdown.rs +0 -0
  101. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/mdx.rs +0 -0
  102. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/mod.rs +0 -0
  103. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/nix.rs +0 -0
  104. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/ocaml.rs +0 -0
  105. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/php.rs +0 -0
  106. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/proto.rs +0 -0
  107. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/python.rs +0 -0
  108. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/ruby.rs +0 -0
  109. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/rust.rs +0 -0
  110. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/scala.rs +0 -0
  111. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/sql.rs +0 -0
  112. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/starlark.rs +0 -0
  113. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/swift.rs +0 -0
  114. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/toml.rs +0 -0
  115. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/tsx.rs +0 -0
  116. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/typescript.rs +0 -0
  117. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/xml.rs +0 -0
  118. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/language_parsers/yaml.rs +0 -0
  119. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/main.rs +0 -0
  120. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/path_arguments.rs +0 -0
  121. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/paths_glob.rs +0 -0
  122. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/repo_path.rs +0 -0
  123. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/report.rs +0 -0
  124. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/sarif.rs +0 -0
  125. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/scan.rs +0 -0
  126. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/settings.rs +0 -0
  127. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/symbol_path.rs +0 -0
  128. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/tag_parser.rs +0 -0
  129. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/src/violation_address.rs +0 -0
  130. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/check_ai.rs +0 -0
  131. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/check_lua.rs +0 -0
  132. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/common/empty.toml +0 -0
  133. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/common/mod.rs +0 -0
  134. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/config.rs +0 -0
  135. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/encoding.rs +0 -0
  136. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/general.rs +0 -0
  137. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/keep_sorted.rs +0 -0
  138. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/keep_unique.rs +0 -0
  139. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/line_count.rs +0 -0
  140. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/line_pattern.rs +0 -0
  141. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/list.rs +0 -0
  142. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/path_arguments.rs +0 -0
  143. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/repository_root.rs +0 -0
  144. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/sarif.rs +0 -0
  145. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/skill.rs +0 -0
  146. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/suppressions.rs +0 -0
  147. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/symbol_references.rs +0 -0
  148. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/cross_file_source.rs +0 -0
  149. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/cross_file_target.md +0 -0
  150. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/custom_ext_source.javascript +0 -0
  151. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/custom_ext_target.javascript +0 -0
  152. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/dangling.md +0 -0
  153. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/missing_symbol.rs +0 -0
  154. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/more_added_than_deleted.py +0 -0
  155. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/multiline_tag_source.rs +0 -0
  156. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/multiline_tag_target.rs +0 -0
  157. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/nested_mapping_source.rs +0 -0
  158. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/nested_mapping_target.yaml +0 -0
  159. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/sibling.md +0 -0
  160. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/symbol_source.rs +0 -0
  161. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/symbol_target.json +0 -0
  162. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/table_source.rs +0 -0
  163. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/table_target.toml +0 -0
  164. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/tag_and_content_source.ts +0 -0
  165. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/tag_and_content_target.ts +0 -0
  166. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/whole_file_source.rs +0 -0
  167. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/affects/whole_file_target.json +0 -0
  168. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_ai/two_blocks.py +0 -0
  169. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/affects.lua +0 -0
  170. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/affects.py +0 -0
  171. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/affects_drift.py +0 -0
  172. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/blocking_io.lua +0 -0
  173. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/blocking_io.py +0 -0
  174. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/echo.lua +0 -0
  175. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/fail.lua +0 -0
  176. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/mode.py +0 -0
  177. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/os.lua +0 -0
  178. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pathfmt/report_path.lua +0 -0
  179. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pathfmt/source.py +0 -0
  180. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pattern.py +0 -0
  181. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pattern_many_matches.py +0 -0
  182. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/pattern_no_match.py +0 -0
  183. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/success.lua +0 -0
  184. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/check_lua/success_and_failure.py +0 -0
  185. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/config.toml +0 -0
  186. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/package.json +0 -0
  187. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/plugins/blue/package.json +0 -0
  188. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/plugins/green/package.json +0 -0
  189. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/config/glob_target/plugins/red/package.json +0 -0
  190. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/general/block_selection.py +0 -0
  191. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/general/custom_file_extension.javascript +0 -0
  192. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/general/custom_file_extension.python +0 -0
  193. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/general/disable_enable.py +0 -0
  194. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/general/paths/invalid.py +0 -0
  195. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/general/paths/subdir/nested_invalid.py +0 -0
  196. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/general/paths/valid.py +0 -0
  197. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/general/severity.py +0 -0
  198. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/hidden/.github/workflows/ci.yml +0 -0
  199. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/keep_sorted/orderings.py +0 -0
  200. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/keep_unique/basic_and_pattern.py +0 -0
  201. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/line_count/equality_and_greater_than.py +0 -0
  202. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/line_pattern/forbidden_text.py +0 -0
  203. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/line_pattern/hex_and_word_patterns.py +0 -0
  204. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/list/.hg/.gitkeep +0 -0
  205. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/list/a.py +0 -0
  206. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/list/b.py +0 -0
  207. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/positions/unicode_columns.py +0 -0
  208. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/custom_ext_source.javascript +0 -0
  209. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/custom_ext_target.javascript +0 -0
  210. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/package.json +0 -0
  211. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/paths/alpha/item.txt +0 -0
  212. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/paths/beta/item.txt +0 -0
  213. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/paths_and_block_source.md +0 -0
  214. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/paths_source.md +0 -0
  215. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/sibling.rs +0 -0
  216. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/source.rs +0 -0
  217. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/split_table_source.rs +0 -0
  218. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/split_table_target.toml +0 -0
  219. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/symbol_mismatch_source.rs +0 -0
  220. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/symbol_source.rs +0 -0
  221. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/target.md +0 -0
  222. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/whole_file_mismatch_source.rs +0 -0
  223. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/whole_file_source.rs +0 -0
  224. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/same_as/whole_file_target.json +0 -0
  225. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/sarif/advisory.py +0 -0
  226. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/sarif/clean.py +0 -0
  227. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/sarif/sorted.py +0 -0
  228. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/sarif/unnamed.py +0 -0
  229. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg.txt +0 -0
  230. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_gone_a.txt +0 -0
  231. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_gone_b.txt +0 -0
  232. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_lowercase_trailer.txt +0 -0
  233. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_malformed.txt +0 -0
  234. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/commit_msg_with_noise.txt +0 -0
  235. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/dangling.py +0 -0
  236. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/sorted.py +0 -0
  237. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/symbol.py +0 -0
  238. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/symbol_target.json +0 -0
  239. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/two_rules.py +0 -0
  240. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/suppressions/unnamed.py +0 -0
  241. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/escapes_source.rs +0 -0
  242. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/escapes_target.json +0 -0
  243. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/filter_source.rs +0 -0
  244. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/filter_target.yaml +0 -0
  245. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/json_source.rs +0 -0
  246. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/json_target.json +0 -0
  247. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/jsonc_source.rs +0 -0
  248. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/jsonc_target.jsonc +0 -0
  249. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/markdown_source.rs +0 -0
  250. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/markdown_target.markdown +0 -0
  251. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/md_source.rs +0 -0
  252. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/md_target.md +0 -0
  253. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/mdx_source.rs +0 -0
  254. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/mdx_target.mdx +0 -0
  255. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/mixed_list_source.rs +0 -0
  256. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/mixed_list_target.json +0 -0
  257. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/toml_source.rs +0 -0
  258. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/toml_target.toml +0 -0
  259. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/yaml_source.rs +0 -0
  260. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/yaml_target.yaml +0 -0
  261. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/yml_source.rs +0 -0
  262. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/symbol_references/yml_target.yml +0 -0
  263. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/affects/pair.py +0 -0
  264. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/clean/counted.py +0 -0
  265. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/clean/noop.py +0 -0
  266. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/clean/notes.txt +0 -0
  267. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/clean/sorted.py +0 -0
  268. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/diff/two_blocks.py +0 -0
  269. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/failing/unsorted.py +0 -0
  270. {blockwatch_cli-0.14.1 → blockwatch_cli-0.14.2}/tests/testdata/verbosity/mixed/both_kinds.py +0 -0
  271. {blockwatch_cli-0.14.1 → 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
@@ -193,7 +193,8 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
193
193
  report goes to stdout. The exit code depends on the error-severity violations that are not suppressed.
194
194
 
195
195
  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.
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.
197
198
 
198
199
  The main modules:
199
200
 
@@ -12,6 +12,38 @@ 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
+
15
47
  ## [0.14.1] - 2026-10-17
16
48
 
17
49
  ### Changed
@@ -193,7 +193,8 @@ The whole pipeline is in `src/cli.rs`. `src/main.rs` only calls `blockwatch::run
193
193
  report goes to stdout. The exit code depends on the error-severity violations that are not suppressed.
194
194
 
195
195
  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.
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.
197
198
 
198
199
  The main modules:
199
200
 
@@ -274,7 +274,7 @@ checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
274
274
 
275
275
  [[package]]
276
276
  name = "blockwatch"
277
- version = "0.14.1"
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.1"
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.1
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
@@ -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.14.2 # 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.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.1 # 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`)
@@ -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.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.1/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
  }
@@ -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.14.2'],
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",
@@ -1,9 +1,10 @@
1
- //! The whole run: reads the flags and the input, runs the validators or `list`, and writes the
2
- //! output.
1
+ //! The whole run: reads the flags and the input, runs the validators, `list` or `explain`, and
2
+ //! writes the output.
3
3
 
4
- use crate::blocks::BlockSeverity;
4
+ use crate::blocks::{BlockAddress, BlockSeverity};
5
5
  use crate::config;
6
6
  use crate::diff_parser::{self, DiffChanges};
7
+ use crate::explain::Explanation;
7
8
  use crate::flags;
8
9
  use crate::fs::FileSystem;
9
10
  use crate::language_parsers;
@@ -12,7 +13,7 @@ use crate::repo_path::RepoPath;
12
13
  use crate::report;
13
14
  use crate::sarif;
14
15
  use crate::scan;
15
- use crate::settings::Settings;
16
+ use crate::settings::{BlockSelection, Settings};
16
17
  use crate::validators;
17
18
  use crate::validators::Violation;
18
19
  use crate::violation_address::ViolationAddress;
@@ -27,7 +28,8 @@ use std::sync::Arc;
27
28
  use std::{env, fs};
28
29
 
29
30
  /// Runs the program with the command-line flags of the process: validates the blocks, lists them
30
- /// with the `list` subcommand, or prints the agent skill with the `skill` subcommand.
31
+ /// with the `list` subcommand, checks one block with the `explain` subcommand, or prints the agent
32
+ /// skill with the `skill` subcommand.
31
33
  ///
32
34
  /// Returns the exit code of the process:
33
35
  /// - `0` when no violation fails the run.
@@ -41,6 +43,7 @@ pub fn run() -> ExitCode {
41
43
  let result = args.validate().and_then(|()| match &args.command {
42
44
  Some(flags::Subcommand::List { .. }) => run_list(&args).map(|()| ExitCode::SUCCESS),
43
45
  Some(flags::Subcommand::Skill) => run_skill().map(|()| ExitCode::SUCCESS),
46
+ Some(flags::Subcommand::Explain { address }) => run_explain(&args, address),
44
47
  None => run_validators(&args),
45
48
  });
46
49
  result.unwrap_or_else(|error| {
@@ -65,6 +68,47 @@ fn run_skill() -> anyhow::Result<()> {
65
68
  write_line(std::io::stdout(), skill.trim_end())
66
69
  }
67
70
 
71
+ /// Runs the `explain` subcommand: checks the one block that `address` selects, writes what its
72
+ /// validators saw to stdout as JSON, and writes its violations to stderr.
73
+ ///
74
+ /// Returns the exit code of a validation run: 1 when a violation of error severity is found, and 0
75
+ /// otherwise.
76
+ fn run_explain(args: &flags::Args, address: &BlockAddress) -> anyhow::Result<ExitCode> {
77
+ let Prepared {
78
+ file_system,
79
+ settings,
80
+ mut context,
81
+ ..
82
+ } = prepare(args)?;
83
+ // The address picks the block, so the config's `only-blocks` and `skip-blocks` play no part.
84
+ context.apply_block_selection(
85
+ &BlockSelection::Only(vec![address.clone()]),
86
+ file_system.as_ref(),
87
+ )?;
88
+ let (sync_validators, async_validators) = validators::detect_validators(
89
+ &context,
90
+ validators::Validator::detect,
91
+ settings.validator_selection(),
92
+ &file_system,
93
+ )?;
94
+ context.request_traces();
95
+ let context = Arc::new(context);
96
+ let log = validators::run(Arc::clone(&context), sync_validators, async_validators)?;
97
+
98
+ // Violations are what the run is for, so they are written first, as in `run_validators`. A
99
+ // failure to write the explanation then can't discard them.
100
+ let has_error_severity = process_violations(&log.violations, flags::OutputFormat::Json)?;
101
+ write_json(
102
+ std::io::stdout(),
103
+ &Explanation::new(&context, &settings, &log, args.diff),
104
+ )?;
105
+ Ok(if has_error_severity {
106
+ ExitCode::from(1)
107
+ } else {
108
+ ExitCode::SUCCESS
109
+ })
110
+ }
111
+
68
112
  /// Runs the `list` subcommand: parses every block in scope and writes a JSON report to stdout.
69
113
  fn run_list(args: &flags::Args) -> anyhow::Result<()> {
70
114
  let Prepared { context, .. } = prepare(args)?;
@@ -0,0 +1,69 @@
1
+ //! The output of the `explain` subcommand.
2
+
3
+ use crate::blocks::{BlockListing, ContentListing};
4
+ use crate::repo_path::RepoPath;
5
+ use crate::settings::Settings;
6
+ use crate::validators::{ValidationContext, ValidationLog, Validator, ValidatorTrace};
7
+ use serde::Serialize;
8
+ use std::collections::{BTreeMap, BTreeSet};
9
+ use strum::IntoEnumIterator;
10
+
11
+ /// What `explain` writes to stdout: one block, the content its validators read, what they saw, and
12
+ /// how the settings treat it.
13
+ ///
14
+ /// The fields are declared in alphabetical order, so the keys come out sorted.
15
+ #[derive(Serialize, Debug)]
16
+ pub(crate) struct Explanation<'a> {
17
+ // <block name="explanation" affects="docs/cli.md:explain-output-example">
18
+ /// The block, as `list` writes it.
19
+ block: BlockListing<'a>,
20
+ /// What each validator saw in the block, by validator.
21
+ checks: BTreeMap<Validator, &'a ValidatorTrace>,
22
+ content: ContentListing<'a>,
23
+ /// The validators that the block's attributes ask for, but the settings turn off.
24
+ disabled: BTreeSet<Validator>,
25
+ file: &'a RepoPath,
26
+ /// Whether the run was given a diff. Without one, no block counts as changed.
27
+ with_diff: bool,
28
+ // </block>
29
+ }
30
+
31
+ impl<'a> Explanation<'a> {
32
+ /// Describes the one block that `context` keeps, as `settings` treat it, with the traces that
33
+ /// `log` holds for it. `with_diff` is whether the run was given a diff.
34
+ ///
35
+ /// # Panics
36
+ /// Panics if `context` doesn't keep exactly one block.
37
+ pub(crate) fn new(
38
+ context: &'a ValidationContext,
39
+ settings: &Settings,
40
+ log: &'a ValidationLog,
41
+ with_diff: bool,
42
+ ) -> Self {
43
+ assert_eq!(context.blocks().len(), 1, "the context keeps one file");
44
+ let (file, file_blocks) = context.blocks().iter().next().expect("one file");
45
+ let [block_with_context] = file_blocks.blocks_with_context.as_slice() else {
46
+ panic!("the context keeps one block");
47
+ };
48
+ let block = &block_with_context.block;
49
+ Self {
50
+ block: BlockListing::from(block_with_context),
51
+ checks: log
52
+ .traces
53
+ .get(&(file.clone(), block.key()))
54
+ .into_iter()
55
+ .flatten()
56
+ .map(|(validator, trace)| (*validator, trace))
57
+ .collect(),
58
+ content: block.content.listing(&file_blocks.file_content),
59
+ disabled: Validator::iter()
60
+ .filter(|validator| {
61
+ block.attributes.contains_key(validator.as_str())
62
+ && !settings.validator_selection().includes(*validator)
63
+ })
64
+ .collect(),
65
+ file,
66
+ with_diff,
67
+ }
68
+ }
69
+ }