# Changelog
All notable user-facing changes to BlockWatch are documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Releases up to and including v0.3.11 predate this file. Their notes live on the
[GitHub releases page](https://github.com/mennanov/blockwatch/releases).
## [Unreleased] - ReleaseDate
## [0.6.0] - 2026-09-15
### Changed
- **Breaking:** the JSON diagnostic contract now defines `range.end.character` as exclusive (`[start, end)`,
advancing it by one past the last highlighted character) to align with SARIF and LSP conventions.
### Fixed
- SARIF violation ranges are now half-open `[start, end)` (1-based, exclusive end character),
fixing single-character highlights appearing as zero-width cursors and off-by-one under-highlighting in SARIF
viewers.
- Unexpected closed block parser errors now report the 1-based character column (`column {}`)
instead of a 0-based byte offset (`position {}`), matching malformed tag errors.
## [0.5.5] - 2026-09-12
### Fixed
- Deleting the lines immediately above a block no longer counts as a change to that block. `affects` reported the
blocks it points at as out of date when nothing needed updating, and — the worse half — treated a target that had
only lost lines above it as updated, silently dropping a violation that should have been reported. With
`--only-changed`, a block left untouched this way is no longer pulled into the run at all, so its other rules stop
re-running too. Deleting every line of a block's content still counts as a change to it.
- Rewriting a block's start tag across two or more extra lines no longer counts as a change to the block's content.
`affects` reported the blocks such a block points at as out of date when nothing needed updating, and — the worse
half — treated a target whose own tag had been rewritten this way as updated, silently dropping a violation that
should have been reported. Reformatting a tag now leaves `is_content_modified` false, whichever language the file
is in.
## [0.5.4] - 2026-09-10
### Added
- `--suppress-from FILE`, repeatable, reads suppression addresses from `Blockwatch-suppress: ADDRESS` lines in a text
file. The prefix is matched case-insensitively and every other line is ignored, so an ordinary commit message is
valid input and a suppression can travel with the commit that needs it instead of living in the CI configuration.
The path may point anywhere the run can read, so a `commit-msg` hook can pass the message file Git hands it even
from a linked worktree, where that file sits outside the tree being checked.
- A second [pre-commit](https://pre-commit.com) hook id, `blockwatch-commit-msg`, runs the same check at the
`commit-msg` stage and feeds the message being written to `--suppress-from`. The original `blockwatch` hook is
unchanged, so an existing configuration keeps working; see [CI Integration](docs/ci.md) for which one to pick and for
the extra install step a `commit-msg` hook needs.
### Fixed
- The `rev:` of the [pre-commit](https://pre-commit.com) snippets in the README and in
[CI Integration](docs/ci.md) named releases that were stale or, in one case, had never been published. Both
now name the current release, and the release process keeps them and the SARIF sample output in step from
here on.
## [0.5.3] - 2026-09-05
### Added
- `--format sarif` writes the violations as a [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html)
log on stderr, in place of the JSON diagnostics, for GitHub code scanning and anything else that reads the format. A
violation's address travels with it as a `partialFingerprints` entry, so a consumer can match it against the same
violation in a later run, and a suppressed violation carries SARIF's own `"suppressions": [{"kind": "external"}]`.
Unlike the JSON diagnostics, a SARIF log is written even by a run that found nothing.
## [0.5.2] - 2026-09-04
### Added
- `--suppress FILE[:BLOCK_NAME[:VALIDATOR[:HASH]]]`, repeatable, stops reported violations failing the run without
editing the source it points at. Blocks with no `name` have no address of their own, so their violations can only be
suppressed by a file-wide address.
### Changed
- Diagnostics carry two new fields: `address` (absent for an unnamed block) and `suppressed` (absent unless true).
## [0.5.1] - 2026-08-28
### Added
- `affects`, `same-as` and `check-lua`'s `ctx.affects` accept a whole file as a target, written without a `:`
(`affects="config/schema.json"`). Nothing is parsed out of the file, so formats that cannot declare a block — JSON,
`.env`, lockfiles, plain-text fixtures — can now be linked to. `affects` counts the target as modified when the diff
touches the file at all; `same-as` compares against the file's entire content, read under the referencing block's
`same-as-pattern`; a `ctx.affects` entry for a whole file carries the file's text and no `name`.
### Fixed
- A `<block>` tag written inside a string is no longer picked up as a real rule in GraphQL, nor in the JSON form of a
Dockerfile instruction (`CMD ["# ..."]`).
### Changed
- A reference whose block name is empty (`affects="config.json:"`) is now rejected as an authoring error instead of
being reported as a dangling reference to a block with no name.
## [0.5.0] - 2026-08-26
### Changed
- A `check-ai-pattern` that matches nothing in its block is now reported as a violation and no request is made. The
model used to be sent an empty string, answer that it met the condition, and leave the block counted as checked
without any of its content having been examined — so a pattern broken by a typo or by content that drifted passed
quietly. `check-lua-pattern` is unchanged: its script still receives an empty array and decides for itself.
- **Breaking:** a `check-lua` script whose block sets `check-lua-pattern` now receives `content` as a 1-based array of
the extracted values instead of a string. A script that expects a single value reads `content[1]`. The argument is an
array whenever the attribute is present — a single match gives a one-element array and a pattern that matches nothing
gives an empty array — so a script never has to branch on the type of its argument. Blocks without
`check-lua-pattern` are unaffected: `content` is still the block's trimmed text.
### Fixed
- Use every match a `*-pattern` finds, in `check-ai`, `check-lua` and `same-as`. Each of them kept only the first match
and silently ignored the rest. Now `check-ai` receives the values joined by newlines, `check-lua` receives them as an
array, and `same-as` compares them as separate items. Matches whose value is empty are skipped everywhere. One
consequence for `same-as`: because every line's matches now flow into one list, two blocks holding the same values
across the lines now agree where they used to differ earlier. Fixes
([#125](https://github.com/mennanov/blockwatch/issues/125)).
- Scan dot-prefixed files and directories, such as `.github/`. The repository walk dropped them before the file patterns
were applied, so blocks there were never validated and an explicit `blockwatch ".github/**"` reported nothing. The
directories a version control system keeps its state in (`.git`, `.hg`, `.jj`, `.svn`) are still skipped, and
`.gitignore` and `--ignore` still apply. Fixes ([#100](https://github.com/mennanov/blockwatch/issues/100)).
## [0.4.4] - 2026-08-25
### Added
- Accept `_` digit separators in `keep-sorted-format="numeric"` and `same-as-format="numeric"`, so long literals can keep
the spelling their language gives them (`1_000_000`). A separator must sit between two digits.
### Fixed
- Apply the `keep-unique` and `keep-sorted-pattern` regexes to the trimmed line. A line whose match is empty is now
skipped like any other unmatched line. Fixes ([#120](https://github.com/mennanov/blockwatch/issues/120)).
- Compare numbers exactly in `keep-sorted-format="numeric"` and `same-as-format="numeric"`. Values that exceed the
precision or the range of a 64-bit float — long identifiers, for instance — no longer compare equal to each other.
`inf` and `NaN` are no longer accepted as numbers. Fixes
([#103](https://github.com/mennanov/blockwatch/issues/103)).
## [0.4.3] - 2026-08-24
### Added
- Add a `check-lua-timeout` block attribute which limits how long a Lua script may run (default: 30 seconds). Fixes
([#107](https://github.com/mennanov/blockwatch/issues/107)).
### Fixed
- `-E` extension mappings now apply to the files `same-as` and `affects` read to resolve a reference, not only to the
files the run scans. A referenced file the mapping made parseable was reported as an unsupported format by `same-as`,
and treated as unchanged by `affects`. Fixes ([#105](https://github.com/mennanov/blockwatch/issues/105)).
- `same-as` now reports a violation when its `same-as-pattern` matches no lines on both the source and target sides,
instead of treating the two empty results as trivially equal and passing. Fixes
([#102](https://github.com/mennanov/blockwatch/issues/102)).
- Duplicate blocks with the same name within the same file are rejected. Fixes
([#104](https://github.com/mennanov/blockwatch/issues/104)).
- A `<block>`/`</block>` tag that fails to parse (for example a missing closing `>`) now fails the whole run with
`Malformed block tag at line N, column N`, instead of being silently skipped. Fixes
([#108](https://github.com/mennanov/blockwatch/issues/108)).
- `affects` now checks that each referenced block still exists, matching `same-as`. A reference to a renamed or deleted
target block is reported as a violation (rather than passing silently, or reporting the misleading "is modified, but X
is not"), and this check runs even without a diff; a reference to a missing target *file* fails the run. Fixes
([#109](https://github.com/mennanov/blockwatch/issues/109)).
## [0.4.2] - 2026-08-21
### Fixed
- A proper handling of non-ASCII chars. Fixes ([#127](https://github.com/mennanov/blockwatch/issues/127)).
- `line-pattern`, `keep-sorted` and `keep-unique` violations now point at the failing text in the source file. Fixes
([#123](https://github.com/mennanov/blockwatch/issues/123)).
- `check-lua` accepts scripts that start with a `#!` line or a UTF-8 byte order mark. Fixes
([#121](https://github.com/mennanov/blockwatch/issues/121)).
- Block markers written inside a Markdown table cell are now found. Fixes
([#116](https://github.com/mennanov/blockwatch/issues/116)).
- Marker text inside a string is no longer read as a block. An HTML attribute value and a quoted Dockerfile argument
each invented a block the source never declared, which could also report a violation against a file nobody had
tagged.
## [0.4.1] - 2026-08-20
### Breaking changes in v0.3.11
- **`check-lua` scripts can no longer read custom block attributes.** This changed in **0.3.11**, as a consequence of
rejecting misspelled attribute names ([#97](https://github.com/mennanov/blockwatch/issues/97)).
### Fixed
- Added support for git worktrees and submodules ([#114](https://github.com/mennanov/blockwatch/issues/114)).
- A change to a block's content is no longer missed when the same edit also touched the block's start tag or the lines
above it ([#106](https://github.com/mennanov/blockwatch/issues/106)).
## [0.4.0] - 2026-08-20
### Changed
- **Breaking:** the run mode is now chosen by flags rather than inferred from stdin. `--diff` supplies the diff and is
the only thing that reads stdin; `--only-changed` narrows the run to the blocks the diff touched. A bare
`git diff | blockwatch` now scans the whole repository instead of the diff — pass both flags for the previous
behavior: `git diff | blockwatch --diff --only-changed`.
- **Breaking:** the `--verbosity summary` line and the JSON report now name the run mode in a `mode` field. Under
`mode=all` they also report `blocks_needing_diff`: the number of blocks carrying a rule, such as `affects`, that
cannot fire without a diff.
- `--diff` now rejects stdin that cannot be a diff — empty input, colorized diff, or text that is not a patch — instead
of silently checking nothing. A valid diff that produces no line changes is still accepted.
- Positional globs now narrow the files a diff selects, so they restrict a run in every mode.
### Fixed
- `affects` resolves its reference targets even when they are excluded by the globs. An out-of-scope file is read only
to answer the reference: it is never validated and never appears in a run report.
- A diff that does not contain a single valid path (likely a mistake) is now an error under
`--diff`, which silently succeeded previously. A single unresolvable path among valid ones remains normal.