docs(ai): ai.md becomes a router; invariants move to AGENTS.md; budgets gated
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.
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# AGENTS — how to work in this repo
|
||||
|
||||
This file is auto-read at the start of every session. The project keeps its
|
||||
memory as a linked map of markdown files — read it before doing anything.
|
||||
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
|
||||
|
||||
@@ -16,9 +17,8 @@ When the user asks for changes that would involve multiple integration tests:
|
||||
> *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 and pushed per work unit. No
|
||||
`taskNN-*` branches, no PRs. The scope discipline above is about test focus,
|
||||
not git mechanics.
|
||||
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:
|
||||
@@ -27,48 +27,53 @@ 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").
|
||||
|
||||
MUST read the architecture guide before planning or modifying the codebase:
|
||||
[`ai.md`](ai.md) (+ [`schema.md`](schema.md) for the memory-map conventions).
|
||||
## Onboarding (in order — and no further)
|
||||
|
||||
MUST read the coding guide before writing tests or code: the [Working
|
||||
rules](#working-rules) in this file — this repo has no `CODING.md`;
|
||||
conventions live here and in `ai.md`.
|
||||
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.
|
||||
|
||||
## Onboarding (in order)
|
||||
`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.
|
||||
|
||||
1. [`schema.md`](schema.md) — the memory-map conventions (what lives where, how to keep it true).
|
||||
2. [`ai.md`](ai.md) — the AI guide: architecture, patterns, decisions, current state.
|
||||
3. [`TASKS.md`](TASKS.md) — the task catalog (index + queue state); open items and API research facts are in [`TASKS/`](TASKS/).
|
||||
4. [`HANDOFF.md`](HANDOFF.md) — current operational state: what's in flight, landmines, next step.
|
||||
5. `<dir>/index.md` — the index for any directory you're about to touch.
|
||||
**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.
|
||||
|
||||
**If `HANDOFF.md` exists, trust it as current state** — no `fsck`, no branch
|
||||
hunting, no file-scanning to re-derive what it already states, unless it points
|
||||
at a problem.
|
||||
## 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 map gets fixed in the same change (stale facts are corrected, not appended).
|
||||
- **Every feature change ships with its memory update in the SAME commit:**
|
||||
`ai.md` for architecture/patterns, `TASKS.md` for status, index files when
|
||||
layout changes. No code commit without its docs — a follow-up "docs backfill"
|
||||
commit is a broken rule, not a style.
|
||||
- **Rewrite `HANDOFF.md` at session end, compaction, or any interruption.**
|
||||
Never end a session with uncommitted work unrecorded — the handoff names the
|
||||
branch, the dirty files, and why it stopped.
|
||||
- **The first time a fact costs a hunt (secrets path, DB path, port, recovery
|
||||
source), record it** in `ai.md`/indexes/`HANDOFF.md` so the next session never
|
||||
re-hunts it.
|
||||
- **Derived-solution rule (record once, then grep):** the moment 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**, before you finish the task. And always **grep `MyMistakes.md` first**
|
||||
when you hit a "I've done this before but have to figure it out again" wall. A
|
||||
solution recorded once ends the re-derivation loop; an un-recorded solution is a
|
||||
guarantee you will re-derive it and make the user sit through it again. This is
|
||||
the fix for the image-shrink incident (2026-08-29): the recipe was never written
|
||||
down, so it had to be worked out from scratch a second time.
|
||||
- **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).
|
||||
@@ -83,13 +88,63 @@ at a problem.
|
||||
- **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. You do not know everything — repeated failure is a research
|
||||
trigger, not a persistence trigger.
|
||||
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 (see `ai.md`).
|
||||
- **No-Fluff Mode is available on request** — unpadded, ruthless review that
|
||||
argues rather than reassures (see `ai.md`). Optional, never the default.
|
||||
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
|
||||
|
||||
@@ -115,12 +170,12 @@ don't make it. A stable file touched by an unrelated task is a bug, not a featur
|
||||
|
||||
### 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.
|
||||
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/scope-check.sh "Models/Scene.cs" "ViewModels/MainViewModel.cs" "MainWindow.xaml"
|
||||
./scripts/verify.sh "Models/Scene.cs" "ViewModels/MainViewModel.cs" "MainWindow.xaml"
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
Reference in New Issue
Block a user