diff --git a/AGENTS.md b/AGENTS.md index 6872ef2..48a0201 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,6 +64,38 @@ at a problem. - **No-Fluff Mode is available on request** — unpadded, ruthless review that argues rather than reassures (see `ai.md`). Optional, never the default. +## Scope Lock + +BEFORE editing any file, declare the exact file list for the task. Every file you +touch must be either in that list OR you must state the specific dependency that +requires it (e.g. "method X's signature changed, callers must update"). No "while +I'm here" edits. No refactoring. No style tweaks. If you spot a problem in a file +you're already editing, note it in HANDOFF.md as a follow-up — do not fix it in +this change. + +> *The commit `d2114c7` touched 11 files across 4 layers because the AI decided +> to refactor the world. This rule exists because of that incident.* + +### Before editing a file — git history scan + +``` +git log --oneline -5 -- +``` + +If the file hasn't been touched in many commits and the current task doesn't +directly require changing it, that is a red flag — stop and justify the edit or +don't make it. A stable file touched by an unrelated task is a bug, not a feature. + +### Pre-commit audit + +Before every commit, run `scripts/scope-check.sh` with the declared file list. +If any file appears in `git diff` but not in the declared scope, either justify +it or revert the change. The script enforces what the rule demands. + +```bash +./scripts/scope-check.sh "Models/Scene.cs" "ViewModels/MainViewModel.cs" "MainWindow.xaml" +``` + ## Build From WSL, ALWAYS use the Windows dotnet host — Linux `dotnet` re-downloads the diff --git a/HANDOFF.md b/HANDOFF.md index 6ff5a7f..a671ed2 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,7 +1,7 @@ # HANDOFF — Session State ## Branch -**`main`** @ `89ab83b`, committed LOCALLY, working tree clean. **NOT pushed — user rule: never +**`main`** @ `67baac6`, committed LOCALLY, working tree clean. **NOT pushed — user rule: never push without explicit instruction (2026-08-24).** No feature branches pre-1.0: all work lands on `main` per work unit. @@ -23,7 +23,10 @@ push without explicit instruction (2026-08-24).** No feature branches pre-1.0: a - `HotkeyConfigDialog.xaml` + `.cs` — click-to-capture, Unbind per row, Reset/Save/Cancel - `ViewModels/MainViewModel.cs` — loads bindings, `OpenHotkeyConfigCommand`, `HotkeyBindingsChanged` callback - `MainWindow.xaml.cs` — passes bindings to manager, re-attaches on dialog save - - Tests: `HotkeyConfigTests` (round-trip, display string, storage serialization) + - Tests: `HotkeyConfigTests` (round-trip, display string, storage serialization) +- **Thumbnail strip hotkey labels** (`d4aa5e1`): each scene shows its current binding (e.g. "Starting F1"). +- **Pull-out tab renamed** to "Stream Settings"; **right-click Start Stream** → Change Account / Logout context menu (`67baac6`). +- **Scope Lock rules** (`AGENTS.md` + `scripts/scope-check.sh`): pre-task scope declaration, git history scan before editing, pre-commit audit script that validates `git diff` against declared file list. Born from the `d2114c7` incident. - Suite: 231 total, 230 pass — only failure is the known pre-existing `AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback`. Build 0 warnings. ## ⚠️ Landmines diff --git a/scripts/scope-check.sh b/scripts/scope-check.sh new file mode 100644 index 0000000..55aaec4 --- /dev/null +++ b/scripts/scope-check.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# scope-check.sh — Validate that git diff only touches declared files. +# Usage: ./scripts/scope-check.sh "file1.cs" "file2.cs" ... +# Exit 0 if all changed files are in scope, 1 if any are outside. +# +# Checks staged, unstaged, and untracked changes against the allowed list. +# Supports glob patterns in the allowed list (e.g. "ytLive.Tests/*"). + +set -euo pipefail + +if [ $# -eq 0 ]; then + echo "Usage: $0 \"file1.cs\" \"file2.cs\" ..." + echo " Pass the declared file list as arguments." + exit 1 +fi + +# Collect allowed patterns into an array +allowed=("$@") + +# Gather all changed files: working tree vs HEAD (tracked) + staged + untracked +changed=$({ + git diff --name-only HEAD 2>/dev/null || true + git diff --name-only --cached 2>/dev/null || true + git ls-files --others --exclude-standard 2>/dev/null || true +} | sort -u) + +if [ -z "$changed" ]; then + echo "✓ No changes detected." + exit 0 +fi + +violations=() +while IFS= read -r file; do + matched=false + for pattern in "${allowed[@]}"; do + if [[ "$file" == $pattern ]]; then + matched=true + break + fi + done + if [ "$matched" = false ]; then + violations+=("$file") + fi +done <<< "$changed" + +if [ ${#violations[@]} -eq 0 ]; then + echo "✓ Scope check passed — all changed files are in scope." + exit 0 +fi + +echo "✗ SCOPE VIOLATION — the following files are outside the declared scope:" +for f in "${violations[@]}"; do + echo " - $f" +done +echo "" +echo "Either justify the edit or revert the change." +exit 1