Files
brain-of-reese/AGENTS.md
T
ducoterra 6880bd0866
Build and Push Containers / build-and-push-app (push) Successful in 1m51s
Build and Push Containers / build-and-push-db (push) Successful in 15s
phase: 180_markdown_inline_links_images
All verification complete. Final state summary:

**Report — phase 180 final verification pass**
- Verified (all already implemented in working tree): `inline()` chain = code→strong→em→linked-image→image→link→headings→lists in `frontend/assets/markdown.js`; `allowLinkUrl`/`allowImageUrl` allowlists (A5), shared `mdAnchorOpen` (target/rel only for absolute http(s)); `####`→`<h5>`, `#####`/`######`→`<h6>`, `#`/`##`→`<h3>`/`###`→`<h4>` unchanged; `.md-link`/`.md-img`/h5-h6 scale in `styles.css` (palette vars only). All surfaces (chat, thinking, restore, viewer, modal) route through the one renderer (pinned in `app.js`/`document.js`).
- Completion criteria:
  - `[text](url)`→`a.md-link`, `javascript:`/`data:` inert — ✅ (node probe + unit + E2E)
  - `![alt](src)`→`img.md-img loading="lazy"` with `alt`; bad src inert — ✅
  - `####`/`#####`/`######` ladder; shallow rungs unchanged — ✅
  - Fenced link/image/`####` stay code; cell-level link renders in `.md-table` — ✅ (node probe)
  - Byte-identity fixture + phase-44/138 suites pass unedited (git-verified untouched; 6 + 3 E2E pass in isolation) — ✅
  - `uv run pytest --cov=app --cov-report=term-missing`: **4198 passed, TOTAL 99%**; `uv run pytest tests/e2e/test_markdown_inline_links_images.py -v --no-cov`: **4 passed**; `uv run ruff check .`: clean; `uv run pyright`: **0 errors** — ✅
- Docs: full README/AGENTS.md review vs the whole project — env-example↔README table complete, page routes match `app/main.py`, AGENTS quick-ref artifacts exist. Fixed 1 inconsistency: README understated the image allowlist (impl per task 02 accepts first segments ≤2 chars, `^.{0,2}/`, not only `./`/`../`); phase-180 paragraphs otherwise accurate.
- Notable: image allowlist `.{0,2}/` is broader than the overview's `^\.{1,2}/` but is what task 02 explicitly specifies and the docstring documents — not a deviation from the tasks; SVG sizing deliberately deferred to phase 181.
- Next pending phase: `181_svg_document_preview`.
2026-10-14 16:03:36 -04:00

6.2 KiB

AGENTS.md — Operating Rules for All Agents on This Repo

  1. Always read .agents/PLAN.md first. It contains the architecture, the LOCKED DECISIONS, the UI/UX standards, the data model, and the roadmap. Do not design anything that contradicts it.
  2. Follow the phased execution protocol in .agents/phases/. Work through .agents/phases/todo/ in numeric order. When a phase is complete, move its directory to .agents/phases/complete/.
  3. Strictly adhere to the LOCKED DECISIONS (PLAN §2). If you believe a LOCKED decision is wrong, stop and flag it — do not silently deviate.
  4. "One Story, One Phase": each user story in .agents/user_stories/ corresponds to a distinct execution phase, and every phase — story-driven or owner-directed — includes its own dedicated Playwright E2E test suite (one test file for the phase, run in isolation).
  5. "UI Structure Check": before finalizing any UI component, verify that it follows the layout principles in .agents/PLAN.md §7 (proper container usage, the 72rem centered reading column — every page rides the 72rem container at every viewport; phase 100 supersedes §7.1's 46rem chat column, full-width tables on Sources — no skinny wasted-space lists; the catalogue tables (RAG, Sources, History) are bounded scroll panels — max-height: min(70vh, 64rem) — with a sticky header row; a clipped action label shows an ellipsis hint (.cell-clip) with the full text kept reachable via the control's aria-label (phase 156); an in-flight turn never paints an empty answer bubble — the box is created empty as the reasoning anchor and .msg .bubble:empty { display: none } hides it until the first answer token (phase 166); an answer's inline [text](url), ![alt](src) and ####+ heading render through the ONE shared renderer into an allowlist-gated a.md-link (underlined, never a javascript:/data: element), a container-capped img.md-img (max-width: 100% — no horizontal overflow at 375px) and an h5/h6 sized into the existing type scale — never printed reference text (phase 180); the document viewer's page strip is CLASS-scoped — the primary class shows the strip in place of the text, the additive class never hides the text: Text/Pages toggle group (aria-pressed, Text default) with the strip behind it (phase 163); a lazily-drilled KB-tree level keeps the level the user is ON rendered while its node fetch runs, and its ONE in-flight row (role="status") / alert + Retry row (role="alert", a real <button>) sits inside that same bounded scroll panel — and the stat cards read the server's totals, never a walk of a depth-limited tree (phase 167)) and meets WCAG 2.1 AA basics (semantic landmarks, labels, contrast ≥4.5:1, focus-visible, aria-live for the stream).
  6. "No CDN Rule": all CSS, JS, fonts, and images must be served statically from within the FastAPI application. No <script src="https://…"> or <link href="https://…"> tags in HTML templates unless bundled locally. An integration test enforces this on the index page.
  7. "Debugpy Check": any new entrypoint must keep the conditional import — debugpy is imported only when DEBUGPY=1 (see app/core/debugging.py); default off, never imported otherwise.
  8. Git protocol: Git is mandatory. One atomic, professional commit per completed phase (Conventional Commits, e.g. feat(rag): stream RAG answers over SSE with source citations). Always append --no-gpg-sign (the repo also sets commit.gpgsign=false). .agents/ is tracked and committed with the phase — only runtime artifacts (.agents/phase-sessions/, .agents/pipeline.log) are gitignored.
  9. Test gates are non-negotiable: a phase is complete only when unit + integration tests pass, app/ coverage is >90% (uv run pytest --cov=app --cov-report=term-missing), and the phase's Playwright E2E file passes in isolation (uv run pytest tests/e2e/test_<story>.py -v --no-cov).
  10. Amply log, visibly feed back: server-side, log the required per-turn line (PLAN §9) and the per-response byte-budget line (app/core/response_budget.py — one api method=… path=… status=… bytes=… enc=… duration_ms=… line per /api/* response, INFO at/over BOR_LOG_LARGE_RESPONSE_BYTES, read it with grep ' :: api ', audit it with uv run python -m scripts.api_payload_audit, phase 167); UI-side, honor the "never stale" feedback contract (PLAN §7.4).
  11. Never block the event loop: anything blocking (a subprocess, synchronous file/network I/O, a heavy walk) reached from an async handler or a spawned background coroutine goes through asyncio.to_thread (or create_subprocess_exec). The sync's git calls do (phase 179: a long git clone used to park the single uvicorn loop and freeze the whole HTTP surface, the 2 s GET /api/sync/status poll included — pinned by tests/integration/test_sync_git_freeze.py with a 3 s stub and by tests/e2e/test_sync_git_freeze.py with a real ~9 s file:// clone). A cooperative cancel unblocks the run at the to_thread await; the worker thread itself runs to completion (threads are not interruptible) while the loop keeps serving — pinned by the same E2E module's cancel-during-clone test.

Quick reference

podman compose up -d db                      # start Postgres 17 + pgvector
cp .env.example .env                         # once; then edit
uv run alembic upgrade head                  # apply migrations
uv run uvicorn app.main:app --reload         # dev server (add DEBUGPY=1 to debug)
uv run pytest                                # unit + integration
uv run pytest --cov=app --cov-report=term-missing
uv run playwright install chromium           # once
uv run pytest tests/e2e/test_smoke.py -v --no-cov
uv run ruff check . && uv run pyright        # lint + types
uv run python -m scripts.eval_retrieval --from-file tests/fixtures/retrieval_battery.txt   # retrieval regression battery (phases 119/162)
uv run python -m scripts.api_payload_audit                      # endpoint byte audit vs the 256 KB budget (phase 167; app must be running)