Files
brain-of-reese/.agent/user_stories/quadlet-jinja-import.md
T
ducoterra 02c76ad328 chore(agent): phase roadmap from TODO.md — 8 phases (40–47), 24 tasks
Converts the 9 TODO items into an executable phase roadmap (Protocol B,
appended after phase 39):

- 40 tuning toggle anonymous flash (TODO L3)
- 41 sync fail-fast + modal when a model is down (TODO L4)
- 42 no reply autoscroll (TODO L5)
- 43 thinking scroll back — user scroll + gated autoscroll (TODO L7)
- 44 markdown tables (TODO L6)
- 45 agent unlimited tool calls behind BOR_AGENT_MAX_ROUNDS (TODO L8)
- 46 mobile hamburger nav (TODO L9)
- 47 quadlet + jinja import formats, A9 revision (TODO L10–L11)

Each phase carries a user story, a dedicated Playwright E2E suite plan,
and owner-locked decisions (R1 A9 format extension, R2 phase-37 budget
revision, A1–A5 scope decisions) confirmed 2026-08-27.

Also records the completed phases 30–39 todo/ -> complete/ moves that
were pending in the working tree. TODO.md is cleared (items now live in
.agent/phases/todo/).
2026-08-27 18:25:53 -04:00

99 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Story: Import quadlet + jinja files
**Phase:** `47_quadlet_jinja_import` · **Source:** `TODO.md` L10–L11 ·
**E2E:** `tests/e2e/test_quadlet_jinja_import.py`
## Bug reports (verbatim, `TODO.md` L10–L11)
> "Add \".container\", \".network\", \".volume\" and other quadlet files
> to the list of allowed/parsed files"
> "Add \".j2\" jinja files to the list of allowed/parsed files"
## Narrative
As **the owner**, my homelab notes increasingly live in Podman quadlet
unit files (`.container`, `.network`, `.volume`, …) and Jinja templates
(`.j2`). Neither is in the A9 import format list, so the KB is blind to
exactly the config files I ask questions about. Both families join the
allowed + default import formats and are parsed (chunked) by the
importer.
- **Given** a source directory containing quadlet and/or `.j2` files
- **When** the importer (CLI or sync) runs
- **Then** those files are indexed (chunked, embedded, upserted) like any
other A9-format file, and their content is retrievable.
## Acceptance criteria
1. **Allowed set:** `_ALLOWED_IMPORT_EXTENSIONS` in `app/config.py`
gains `container, network, volume, image, pod, kube, swap, os,
endpoint` (the full Podman quadlet family) and `j2`; a
`BOR_IMPORT_EXTENSIONS` env value may name any of them (the
never-widen validator keeps rejecting truly unknown extensions).
2. **Default set:** the default `import_extensions` CSV includes all ten
new formats after the existing seven — a default import now picks
them up with no env configuration.
3. **Parsing:** `chunker.py` dispatches every new suffix to
plain-text paragraph packing (`chunk_text`) — quadlet files are TOML
unit files and `.j2` files are templates; no format-specific
splitter (owner decision). Every chunk still honors
`HARD_MAX_CHARS` (1200).
4. **Title:** no H1 → `extract_title` falls back to the file stem, as
with other non-markdown formats (no change needed, verified).
5. **Behavior parity:** hidden (dot) directories are still skipped, the
exclusion list is unchanged, sha256 delta detection / prune work
unchanged for the new formats.
6. **Docs:** `.env.example`'s `BOR_IMPORT_EXTENSIONS` comment, the
README's format list, and an **A9 revision note** in
`.agent/PLAN.md` (owner permission 2026-08-27, `TODO.md` L10–L11)
record the extended set.
## Owner-confirmed (2026-08-27, roadmap R1)
1. **Full quadlet family:** `container, network, volume, image, pod,
kube, swap, os, endpoint` — plus `j2`.
2. **Chunked as plain text** — no TOML/Jinja-aware splitting.
3. **A9 is revised** with a PLAN.md revision note (the established
owner-permission pattern).
## UI Visualization & Structure
- **Config** (`app/config.py`): extend the `_ALLOWED_IMPORT_EXTENSIONS`
frozenset + the `import_extensions` default string (comment cites the
A9 revision 2026-08-27). No validator change — it already
normalizes/dedups and rejects unknowns.
- **Chunker** (`app/rag/chunker.py`): ten new `_FORMAT_CHUNKERS` entries
→ `chunk_text`; module docstring's format list updated.
- **Fixtures:** `tests/fixtures/docs/homelab/quadlet/compose.container`
(realistic quadlet TOML: `[Unit]` / `[Service]` / `[Container]`
sections, one unique sentinel token, >1200 chars to exercise
sub-splitting), `…/quadlet/lan.network`, `…/quadlet/cache.volume`,
and `tests/fixtures/docs/homelab/templates/deploy.j2` (Jinja snippet
with `{{ … }}` / `{% … %}` tags + its own sentinel).
- **Tests:** unit (`test_config.py` allowed-set/default/validator;
`test_chunker.py` dispatch for every new suffix + fixture chunking;
`test_importer.py` directory walk picks the new files up);
integration (import_sources over a temp dir with quadlet+j2 files →
documents + chunks rows).
- **Non-goals:** no new DB column, no format badge change (the viewer
shows the extension it already shows), no summary-model changes.
## Playwright Mapping Rule
**Test Scenario → `tests/e2e/test_quadlet_jinja_import.py`** (mock
LLM; DB up):
1. `test_quadlet_and_jinja_indexed` — truncate + import the fixture
tree (the `test_import_documents.py` pattern): `GET /api/docs` lists
the `.container` / `.network` / `.volume` / `.j2` files with
non-zero chunk counts.
2. `test_sources_table_shows_them` (admin) — the Sources table renders
rows for the new files; their path links open the document modal.
3. `test_container_content_viewable` — the viewer modal shows the
`.container` file's TOML content (sentinel token present) with its
stem as the title.
4. `test_jinja_retrievable_not_deflected` — ask a question containing
the `.j2` file's sentinel word: the FTS hit keeps the honesty gate
honest-positive (A8) — the answer bubble is **not**
`.is-deflected` and the source chip names the `.j2` document.
5. `test_default_walk_includes_new_formats` (regression, unit-backed) —
a default-extensions walk over a temp tree with all ten new
extensions indexes every file; hidden directories + the exclusion
list still filter (covered by `test_importer.py` in the regression
pass).