Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3124,6 +3124,18 @@
authored entirely by the fork.
### Added

- **`scripts/dev/resolve-state-md-conflict.py`** — resolves a `docs/state.md`
rebase conflict the way ADR-0165 requires, instead of by hand. `state.md` is
deliberately excluded from the `merge=union` list in `.gitattributes` because
its rows move between the "Open bugs" and "Recently closed" sections, so a
keep-both resolution duplicates the row and leaves a closed bug reading as
open. The script takes master's side whole and appends only the rows master
does not have, deduplicating by bug id so a row master reworded is not
re-added in its stale form. `scripts/ci/check-state-md-rows.sh` now names it
in the failure message, and `docs/development/ci.md` documents the rule it
encodes and the case it cannot decide for you.


- **`speed_qa` real SpEED-QA implementation** (`core/src/feature/speed_qa.c`):
replaces the 0.0-placeholder scaffold with a working spatial and temporal
entropic-differencing extractor per Bampis et al. 2017 (IEEE SPL 24(9)).
Expand Down
10 changes: 10 additions & 0 deletions changelog.d/added/0165-state-md-conflict-resolver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- **`scripts/dev/resolve-state-md-conflict.py`** — resolves a `docs/state.md`
rebase conflict the way ADR-0165 requires, instead of by hand. `state.md` is
deliberately excluded from the `merge=union` list in `.gitattributes` because
its rows move between the "Open bugs" and "Recently closed" sections, so a
keep-both resolution duplicates the row and leaves a closed bug reading as
open. The script takes master's side whole and appends only the rows master
does not have, deduplicating by bug id so a row master reworded is not
re-added in its stale form. `scripts/ci/check-state-md-rows.sh` now names it
in the failure message, and `docs/development/ci.md` documents the rule it
encodes and the case it cannot decide for you.
43 changes: 43 additions & 0 deletions docs/development/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,49 @@ to CI:
| … excludes `core/src/mcp/`, `core/test/test_mcp*`, `core/test/fuzz/`, `core/src/compat/win32/`, `core/tools/vmaf_vpl.c` | needs `-Denable_mcp=true` / fuzz / libva / MinGW compile DBs | add those TUs to the cpu-lane build in CI |
| `.cppcheck-suppressions.txt` per-file suppressions, `.clang-tidy` disabled checks, `.semgrep.yml` path excludes, `pyproject.toml` per-file ignores | none — each is a fix-the-code item | rework waves; each removal is a ratchet decrease |

## Resolving a `docs/state.md` rebase conflict

[`docs/state.md`](../state.md) conflicts on almost every rebase of a branch
that touches it, and unlike the other append-only bookkeeping files it is
**deliberately not** in the `merge=union` list in
[`.gitattributes`](../../.gitattributes). Its rows *move* between the
"Open bugs" and "Recently closed" sections, so a union merge would duplicate
the row and leave a closed bug reading as open forever.

That makes "keep both sides" the tempting wrong answer, and
`scripts/ci/check-state-md-rows.sh` exists to catch it. The correct rule is:

- **master's side wins for any row both sides carry.** master is the more
advanced state — it already has every row merged ahead of your branch,
including one your branch also touches but that master has since moved or
reworded.
- **your branch contributes only rows master does not have at all** — its own
new bug id.

[`scripts/dev/resolve-state-md-conflict.py`](../../scripts/dev/resolve-state-md-conflict.py)
applies exactly that, deduplicating by bug id rather than by line so a row
master reworded is not re-added in its stale form:

```bash
# mid-rebase, with docs/state.md conflicted
python3 scripts/dev/resolve-state-md-conflict.py docs/state.md
scripts/ci/check-state-md-rows.sh # always verify
git add docs/state.md && git rebase --continue
```

The verification step is not optional. The script encodes the common case —
a branch adding one new row against a master that has moved others. It cannot
know that a row *your* branch moved to "Recently closed" should win over
master's older "Open bugs" copy; there it keeps master's, and you redo the
move by hand. The row count the gate prints is the cheapest way to notice.

Its own regression test is
[`scripts/dev/test-resolve-state-md-conflict.py`](../../scripts/dev/test-resolve-state-md-conflict.py):

```bash
python3 scripts/dev/test-resolve-state-md-conflict.py
```

## Bug-status hygiene gate (ADR-0165 / ADR-0334)

Per [CLAUDE.md §12 rule 13](../../CLAUDE.md) and
Expand Down
4 changes: 4 additions & 0 deletions scripts/ci/check-state-md-rows.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ if [[ -n "$dupes" ]]; then
echo "" >&2
echo "A keep-both rebase resolution usually caused this: keep the row that" >&2
echo "matches the bug's real state and delete the other (ADR-0165)." >&2
echo "" >&2
echo "Mid-rebase, this resolves the common case for you:" >&2
echo " python3 scripts/dev/resolve-state-md-conflict.py docs/state.md" >&2
echo "then re-run this check before 'git rebase --continue'." >&2
exit 1
fi

Expand Down
Empty file modified scripts/dev/hw_encoder_corpus.py
100644 → 100755
Empty file.
Empty file modified scripts/dev/project_modernization_audit.py
100644 → 100755
Empty file.
120 changes: 120 additions & 0 deletions scripts/dev/resolve-state-md-conflict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
# scripts/dev/resolve-state-md-conflict.py — resolve a docs/state.md rebase
# conflict the way ADR-0165 requires.
#
# Copyright 2026 Lusoris
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
"""Resolve a docs/state.md rebase conflict the way ADR-0165 requires.

`docs/state.md` is deliberately excluded from the `merge=union` list in
`.gitattributes`, unlike the other append-only bookkeeping files. Its rows
MOVE between the "Open bugs" and "Recently closed" sections, so a keep-both
resolution duplicates the row AND leaves the closed bug reading as open
forever — which is exactly what `scripts/ci/check-state-md-rows.sh` gates.

That leaves every rebase of a state.md-touching branch needing a by-hand
resolution, and "keep both sides" is the tempting wrong answer. This script
applies the correct rule instead:

* master's side ("ours" during a rebase) is the more advanced state — it
already carries every row merged ahead of this branch, including any row
this branch also touches but that master has since moved or reworded.
* the branch's side ("theirs") contributes only the rows master does not
have at all: this PR's own new bug id.

So it takes ours whole, then appends only those theirs-rows whose bug id is
not already present. Deduplication is by bug id rather than by line, so a row
master reworded is not re-added in its stale form.

Usage, mid-rebase:

python3 scripts/dev/resolve-state-md-conflict.py docs/state.md
scripts/ci/check-state-md-rows.sh # always verify before continuing
git add docs/state.md && git rebase --continue

The verification step is not optional. This script encodes the common case —
a branch adding one new row against a master that has moved others. It cannot
know that a row your branch MOVED to "Recently closed" should win over
master's older "Open bugs" copy; in that situation it keeps master's, and you
have to redo the move by hand. The row count in the gate's output is the
cheapest way to notice.
"""

import re
import sys
from pathlib import Path

ID_RE = re.compile(r"\*\*(T-[A-Z0-9-]+)\*\*")

# Built rather than written literally so this file does not itself trip the
# `no-conflict-markers` pre-commit hook.
OURS_MARK = "<" * 7
SPLIT_MARK = "=" * 7
THEIRS_MARK = ">" * 7


def bug_id(line):
"""Return the T-… bug id a state.md row declares, or None for other text."""
match = ID_RE.search(line)
return match.group(1) if match else None


def resolve(path):
"""Rewrite `path` in place, resolving every conflict hunk. Returns the count."""
lines = Path(path).read_text(encoding="utf-8").split("\n")
out = []
index = 0
hunks = 0
while index < len(lines):
if not lines[index].startswith(OURS_MARK):
out.append(lines[index])
index += 1
continue

ours = []
theirs = []
index += 1
while not lines[index].startswith(SPLIT_MARK):
ours.append(lines[index])
index += 1
index += 1
while not lines[index].startswith(THEIRS_MARK):
theirs.append(lines[index])
index += 1
index += 1
hunks += 1

# master's side wins wholesale; the branch adds only unseen bug ids.
out.extend(ours)
seen = {bug_id(line) for line in out if bug_id(line)}
for line in theirs:
bid = bug_id(line)
if bid is None:
# Non-row text (a blank line, a heading): keep it only when the
# ours side did not already supply it.
if line.strip() and line not in ours:
out.append(line)
elif bid not in seen:
out.append(line)
seen.add(bid)

Path(path).write_text("\n".join(out), encoding="utf-8")
return hunks


MIN_ARGV = 2


def main(argv):
if len(argv) < MIN_ARGV:
print(f"usage: {argv[0]} docs/state.md [...]", file=sys.stderr)
return 2
for path in argv[1:]:
count = resolve(path)
print(f"resolved {count} hunk(s) in {path} (ours + branch-only rows)")
return 0


if __name__ == "__main__":
sys.exit(main(sys.argv))
85 changes: 85 additions & 0 deletions scripts/dev/test-resolve-state-md-conflict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
# scripts/dev/test-resolve-state-md-conflict.py — regression test for the
# docs/state.md conflict resolver.
#
# Copyright 2026 Lusoris
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
"""Feed the resolver a synthetic conflict covering the three cases that matter.

* a row only master has -> survives untouched
* a row both sides have -> master's wording wins, exactly once
* a row only the branch has, duplicated by an earlier bad rebase
-> kept exactly once

Usage:
python3 scripts/dev/test-resolve-state-md-conflict.py \
[scripts/dev/resolve-state-md-conflict.py]
"""

import subprocess
import sys
import tempfile
from pathlib import Path

# Assembled rather than written literally so this file does not itself trip the
# `no-conflict-markers` pre-commit hook.
CONFLICT = "\n".join(
[
"# state",
"## Open bugs",
"",
"| **T-MASTER-ROW-2026-09-01** | master's own new bug. | ref |",
"<" * 7 + " ours",
"| **T-SHARED-ROW-2026-08-01** | master reworded this row. | ref |",
"=" * 7,
"| **T-SHARED-ROW-2026-08-01** | the branch's stale copy. | ref |",
"| **T-BRANCH-ONLY-2026-09-07** | the branch's own new bug. | ref |",
"| **T-BRANCH-ONLY-2026-09-07** | duplicated by a bad rebase. | ref |",
">" * 7 + " theirs",
"",
]
)

DEFAULT_SCRIPT = Path(__file__).with_name("resolve-state-md-conflict.py")


def check(out):
"""Return the list of assertion failures for a resolved state.md body."""
failures = []
if any(mark * 7 in out for mark in ("<", "=", ">")):
failures.append("conflict markers survived")
if out.count("T-SHARED-ROW-2026-08-01") != 1:
failures.append("shared row not deduplicated")
if "master reworded this row" not in out:
failures.append("master's wording lost")
if "the branch's stale copy" in out:
failures.append("branch's stale copy kept")
if out.count("T-BRANCH-ONLY-2026-09-07") != 1:
failures.append("branch-only row not deduplicated")
if "T-MASTER-ROW-2026-09-01" not in out:
failures.append("untouched master row lost")
return failures


def main(argv):
script = Path(argv[1]).resolve() if len(argv) > 1 else DEFAULT_SCRIPT.resolve()
with tempfile.TemporaryDirectory() as tmp:
target = Path(tmp) / "state.md"
target.write_text(CONFLICT, encoding="utf-8")
subprocess.run( # noqa: S603 — fixed argv, no shell, no user input
[sys.executable, str(script), str(target)],
check=True,
)
failures = check(target.read_text(encoding="utf-8"))

if failures:
for failure in failures:
print(f"FAIL: {failure}", file=sys.stderr)
return 1
print("PASS: all 6 assertions")
return 0


if __name__ == "__main__":
sys.exit(main(sys.argv))
Empty file modified scripts/dev/test_project_modernization_audit.py
100644 → 100755
Empty file.
Empty file modified scripts/dev/test_training_discovery_report.py
100644 → 100755
Empty file.
Empty file modified scripts/dev/vif_log2_poly_check.py
100644 → 100755
Empty file.
Loading