ai.md 228 → 64 lines. It now carries only: what the product is, the build commands, the architecture table, a "where the truth lives" table, and a pointer to HANDOFF. No narrative, no status, no duplicated invariants. AGENTS.md (195 lines) is now the single always-read file: - the 20 invariants that break output, wipe user data, or ship a legal/trust breach — one line each, pointing at the doc that explains it (they replace 110 lines of invariant stubs that duplicated docs/ai/*.md verbatim) - Response style + No-Fluff Mode moved in from ai.md (rules belong in the rules file) - onboarding is now: HANDOFF → ai.md router → the ONE docs/ai file → dir index; TASKS.md is read when the question is "what's next", not to write code - memory rides with the change but is no longer a unit of its own; HANDOFF is ≤40 lines and overwritten, never appended Anti-rebloat gate: scripts/verify.sh gains step 1/4, a doc-budget check (AGENTS ≤210, ai ≤100; HANDOFF/TASKS caps land with the queue trim). A commit that pads a capped file instead of moving detail fails the gate. schema.md and docs/index.md corrected for the new roles and reading order. Docs-only unit: scope-check run, no build.
196 lines
11 KiB
Markdown
196 lines
11 KiB
Markdown
# AGENTS — how to work in this repo
|
||
|
||
Auto-read at the start of every session: the rules, the invariants that break the product, and
|
||
the map to everything else. `ai.md` is the router, `docs/ai/*.md` is the subsystem truth,
|
||
`HANDOFF.md` is current state.
|
||
|
||
## ⚠️ CRITICAL: The Good Dog Rule
|
||
|
||
BEFORE any planning or coding, follow this rule:
|
||
|
||
> ONE integration test per change.
|
||
|
||
When the user asks for changes that would involve multiple integration tests:
|
||
1. Identify the ONE specific use case/test they want to work on
|
||
2. Focus ONLY on the single use case until it's complete and tests pass
|
||
|
||
> *A good dog learns one trick perfectly before learning the next.*
|
||
|
||
**No feature branches pre-1.0 (decided 2026-08-24):** solo dev, pre-1.0 — all
|
||
work lands directly on `main`, committed per work unit. No `taskNN-*` branches, no PRs. The scope
|
||
discipline above is about test focus, not git mechanics.
|
||
|
||
**Commit / push policy (2026-08-30, user directive):** commit every work unit —
|
||
committing is expected and never needs extra permission. Pushing is different:
|
||
push ONLY at sub-milestones and milestones. During a multi-commit refactor,
|
||
that means commits land locally every partial; `git push` happens at the
|
||
session's agreed checkpoint(s), not per-commit. When a milestone warrants a
|
||
push the user will say so (or say "push it").
|
||
|
||
## Onboarding (in order — and no further)
|
||
|
||
1. [`HANDOFF.md`](HANDOFF.md) — current state: branch, what's in flight, next action, landmines.
|
||
**Trust it**; no branch hunting, no file-scanning to re-derive what it already says.
|
||
2. [`ai.md`](ai.md) — the router: what the product is, the architecture table, and where each
|
||
subsystem's truth lives.
|
||
3. **The one** [`docs/ai/`](docs/ai/index.md) file that matches your task — not all of them.
|
||
The routing table at the top of `ai.md` picks it.
|
||
4. `<dir>/index.md` — the index for any directory you're about to touch.
|
||
|
||
`TASKS.md` is the queue. Read it when the user asks what's next or status is part of the
|
||
question — it is not required reading to write code.
|
||
|
||
**Why it is this short:** the mandatory read used to be ~56k tokens of map, so sessions paid it
|
||
and then skimmed — which is exactly how stale facts survive. Read the one subsystem you touch.
|
||
|
||
## Reading budget
|
||
|
||
Enforced by `scripts/verify.sh`; a commit that pads instead of replacing fails the gate.
|
||
|
||
| File | Cap | Role |
|
||
|---|---|---|
|
||
| `AGENTS.md` | 210 lines | rules + invariants, always read |
|
||
| `ai.md` | 100 lines | router only — no narrative, no status |
|
||
| `HANDOFF.md` | 40 lines | current state, overwritten every time |
|
||
| `TASKS.md` | 120 lines | the active queue; done work moves to `TASKS/archive.md` |
|
||
|
||
If a fact does not fit, it belongs in `docs/ai/<subsystem>.md`, `MyMistakes.md`, or `git log`.
|
||
|
||
## Working rules
|
||
|
||
- **Never work without the map.** If the map contradicts the code, the code wins and the facts
|
||
your change **falsifies** are corrected in the same commit (stale facts are corrected, not
|
||
appended). Cosmetic drift — a stale task number, a heading — may wait for a sweep; it is not a
|
||
reason to stall a unit.
|
||
- **Memory rides with the change, not as a unit of its own.** Correct what the change
|
||
falsifies in the same commit; a broad restatement can be batched at a milestone.
|
||
- **`HANDOFF.md` is ≤40 lines and is overwritten, never appended:** branch, in flight, dirty
|
||
files, next action, new landmines. History belongs to `git log`.
|
||
- **The first time a fact costs a hunt, record it where it will be found:** paths / ports /
|
||
secrets / build commands → `docs/ai/environment.md`; subsystem traps → that subsystem's
|
||
`docs/ai` file; one-off recipes → `MyMistakes.md` → Recipes registry.
|
||
- **Derived-solution rule (record once, then grep):** when you work out a reusable solution — a
|
||
recipe, workaround, or how-to (e.g. how to shrink and re-encode an image for the README) —
|
||
write it into `MyMistakes.md` **in the same session**, and **grep `MyMistakes.md` first** on
|
||
any "I've done this before but have to figure it out again" wall. An un-recorded solution
|
||
guarantees the user hears it derived again (the image-shrink incident, 2026-08-29).
|
||
- **Follow existing conventions** — MVVM, `RelayCommand` for actions,
|
||
`ViewModelBase.SetProperty<T>()`, all styles in `Themes/Controls.xaml`
|
||
(merged once in `App.xaml`; never duplicate per-window).
|
||
- **Do not add comments unless the code needs them; do not expand the task queue
|
||
on your own** — work only what the user queues.
|
||
- **Derivative work / spin guard.** Every problem here is derivative — OBS, other
|
||
overlay tools, and the WPF/WebView2 ecosystems already solved it. Both sides are
|
||
mandatory, not advisory:
|
||
- **Proactive (new features):** before writing code, do a quick external scan
|
||
(websearch: how do OBS/CEV/overlay tools do this?) and cite the reference in the
|
||
commit message.
|
||
- **Spin guard (objectively triggered):** a SECOND failed remedy for the same
|
||
symptom = STOP. No third guess over the same code. Research the established
|
||
answer externally, cite its URL in the commit message AND in `MyMistakes.md`,
|
||
then code it. Repeated failure is a research trigger, not a persistence trigger.
|
||
- **Response style: no default planning template.** Do the work, then report
|
||
what changed and what's next. No "Plans & Pitfalls", pros/cons tables, or
|
||
step-by-step plans unless the user asks for a plan first. A short diff-style summary beats a
|
||
proposal document every time.
|
||
- **No-Fluff Mode is available on request.** Invoke with "no-fluff mode" (or similar) for
|
||
ruthless review instead of reassurance: strip pleasantries, emojis and padding; treat the
|
||
user's input as a draft to deconstruct; give unvarnished truth. Occasional and
|
||
explicitly-invoked — never the default.
|
||
|
||
## Invariants that break the product
|
||
|
||
Violating any of these breaks output, wipes a user's data, or ships a legal/trust breach. Each
|
||
line points at the file that explains it.
|
||
|
||
- **The Background is one locked layer per scene** — index 0, never addable/removable/reorderable;
|
||
Live is `DisplayCapture`, every other canonical scene is static art. Never feed a capture row to
|
||
the `ActiveBackgroundImage` overlay. → `docs/ai/capture.md`
|
||
- **Every global overlay composites on every render path** — pump, preview/thumbnails, scene dump.
|
||
A feature wired into one path is silently absent from the output. → `docs/ai/overlays.md`
|
||
- **`VideoFrame.Epoch` bumps on every frame from a recycling producer**, or the pump's paste cache
|
||
and full-render signature serve stale pixels. → `docs/ai/frame-pipeline.md`
|
||
- **Never demote threads inside `Parallel.For`** — `StripPool` owns four `BelowNormal` threads;
|
||
strips below 64 rows run inline. → `docs/ai/frame-pipeline.md`
|
||
- **Submit never blocks:** the encoder queue is bounded, the newest frame is dropped and counted,
|
||
and `StopAsync` flushes the encoder before awaiting the pump. → `docs/ai/frame-pipeline.md`
|
||
- **Audio closes before video:** `StopStream` runs `StopLive` (audio EOF) before stopping the pump;
|
||
the reverse order stalls on pipe backpressure. → `docs/ai/output.md`
|
||
- **rawvideo is paced by arrival, never by a timestamp** — no `-re`, and `nextTick` is never
|
||
rebased to wall-now (that turns a hitch into a silent time-lapse). → `docs/ai/output.md`
|
||
- **The encoder is probed, never libx264** — NVENC → QSV → AMF → OpenH264; no static or GPL codec
|
||
path. → `docs/ai/output.md`
|
||
- **REC and ON-AIR are radio-exclusive** — one ffmpeg, one output, in the UI and the VM.
|
||
→ `docs/ai/output.md`
|
||
- **ffmpeg is bundled and separate** — never downloaded at runtime, never linked into the app; the
|
||
pin stays `lgpl-shared`. → `docs/ai/product.md`
|
||
- **A modal's falsy result never falls through into its side effect** — Cancel on the recording
|
||
save dialog deletes the temp footage. → `docs/ai/output.md`
|
||
- **Any test that constructs `MainWindow` must point `LayoutPathOverride` at a temp DB** and clear
|
||
it in `finally`, or it overwrites the user's real layout. → `docs/ai/environment.md`
|
||
- **The vertical tier is geometry only**, and the Chat camera is rect-locked in the MODEL
|
||
(`VerticalShapeLocked`), not just in the UI. → `docs/ai/capture.md`
|
||
- **Never override the user's camera choice** — there is deliberately no fallback camera.
|
||
→ `docs/ai/capture.md`
|
||
- **Chat is rendered, never stored** — no history, no moderation log, no reward payloads; the
|
||
Store 11.12 certification answer depends on it. → `docs/ai/product.md`
|
||
- **Full trust only** — a packaged build flipped to `appContainer` silently loses recordings.
|
||
→ `docs/ai/product.md`
|
||
- **A `PopupRoot` has no visual parent** — ancestor walks must fall back to
|
||
`LogicalTreeHelper.GetParent`, or a popup row click reads as an outside click.
|
||
→ `docs/ai/ui-surface.md`
|
||
- **Licensing: no non-CC0 icon art, no dropped `THIRD-PARTY-NOTICES.txt`, no
|
||
licence-incompatible dependency, no unvetted ML model.** → `docs/ai/product.md`
|
||
- **The private-only broadcast lock is deliberate** — do not unlock it, do not raise it as a
|
||
question; it ends at v1 by the creator's ruling. → `docs/ai/product.md`
|
||
- **Read render numbers in Release only** — Debug pixel paths are several× slower.
|
||
→ `docs/ai/environment.md`
|
||
|
||
## 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 -- <file>
|
||
```
|
||
|
||
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
|
||
|
||
One gate: `./scripts/verify.sh "<file1>" "<file2>" ...` with the declared scope. It runs the doc
|
||
budgets, a **clean** build (0 warnings), the test suite, and `scope-check.sh`. Docs-only units run
|
||
`scope-check.sh` alone.
|
||
|
||
```bash
|
||
./scripts/verify.sh "Models/Scene.cs" "ViewModels/MainViewModel.cs" "MainWindow.xaml"
|
||
```
|
||
|
||
## Build
|
||
|
||
From WSL, ALWAYS use the Windows dotnet host — Linux `dotnet` re-downloads the
|
||
`windowsdesktop.app.*` packs over the slow 9p bridge and re-restores twice (WPF
|
||
`_wpftmp`), and `--no-restore` right after an interrupted restore produces bogus
|
||
`NETSDK1064` errors. See `docs/ai/environment.md` → Run for the exact commands and why.
|
||
|
||
```bash
|
||
"/mnt/c/Program Files/dotnet/dotnet.exe" build "C:\Users\gramp\Documents\Code\projects\ytLive\ytLive.csproj"
|
||
"/mnt/c/Program Files/dotnet/dotnet.exe" vstest "C:\Users\gramp\Documents\Code\projects\ytLive\ytLive.Tests\bin\Debug\net8.0-windows10.0.19041.0\ytLive.Tests.dll"
|
||
```
|
||
|
||
Keep it at **0 warnings**. Running requires Windows. On a silent startup crash,
|
||
read `%APPDATA%\ytLlive\startup.log` (`Helpers/AppLog.cs` writes checkpoints and
|
||
unhandled exceptions there).
|