feat(docs): save the whole chat session as a doc
Build and Push Containers / build-and-push-app (push) Successful in 1m46s
Build and Push Containers / build-and-push-db (push) Successful in 12s

Phase 75 (TODO.md L4): "Save as doc" now drafts a document from the
ENTIRE chat session — every question and answer up to the click, in
order — instead of only the clicked bubble's answer; the existing
doc-edit screen's free-form body editing is how the user edits out
anything they don't want to keep from previous replies (no new UI
surface).

Task 01 (frontend):
- app.js buildSessionTranscript(): walks the bor.chat.v1 conversation
  record in order — a numbered section per user turn ("## N.
  <question, raw>" + blank line + the raw answer text; more answers
  join under the same heading), sections blank-line separated, all
  trailing whitespace collapsed to one final newline. Only the raw
  persisted text travels (m.who + m.text — no thinking blocks, no
  source chips, no tune metadata); a brain record before the first
  user record is skipped; a heading-only section marks a user turn
  whose answer never landed (A6, owner-confirmed 2026-09-08).
- saveAsDoc(btn): the draft body is buildSessionTranscript(); the
  dead single-bubble markdown parameter is dropped (the button's
  appendSaveAsDocButton signature is unchanged — one button per
  bubble). Title/path/double-click guard/hand-off are unchanged
  (defaultDocTitle: the last question, whitespace-collapsed,
  <=120 chars; docs/<slug>.md).
- Unit: the app.js source pins move to the transcript shape (whole
  session, no thinking, no dead parameter).

Task 02 (E2E):
- tests/e2e/test_save_doc_session.py (bare-repo fixture, the
  phase-59 convention — git as source of truth): three DISTINCT
  on-topic turns in one session (turn 1 carries the phase-17
  "think out loud" trigger so its record has a thinking block the
  transcript must exclude) -> save on the LAST bubble -> the
  prefilled body is ## 1./## 2./## 3. in order, byte-exact against
  the deterministic mock, thinking-free -> edit the whole
  section-2 block out of the body -> push -> git show
  bor-docs:<path> equals the EDITED body byte-for-byte (section 2's
  question and answer provably absent; sections 1 and 3 byte-exact;
  the UI's sha prefix is git rev-parse bor-docs). Second test:
  the button on the FIRST bubble still drafts the whole session
  (A6 — the transcript is the session at click time, title stays
  the last question); canceling leaves the branch tip untouched.
- tests/e2e/test_response_to_docs.py: the phase-59 single-turn body
  expectation moves to the transcript shape ("## 1. <question>" +
  the answer's markdown) — the rest of the suite unchanged.

Also lands the phase-74 file moves (00_phase.md /
03_mock_marker_e2e.md -> complete/) and the phase reports — the
house convention of committing .agents/ with the phase.
This commit is contained in:
2026-09-05 16:59:31 -04:00
parent 055c0b5d85
commit 0e4651c779
16 changed files with 974 additions and 34 deletions
@@ -0,0 +1,11 @@
**Phase 74 final verification pass — all green** (work was already implemented & committed; this pass verified every criterion)
- Verified: `ChatRequest.history` + `history_to_messages` mapper (turn/char budgets, drop-whole, `reasoning_content` on brain turns only when non-empty); wired through both deflected and grounded branches; `runTurn` sends `conversation.slice(0,-1)` with `thinking`; mock `echo my history` marker; per-turn log gains `history_msgs=N` (seen live in e2e app logs).
- `uv run pytest --cov=app` → all pass, TOTAL coverage **99%** (>90% criterion).
- `uv run ruff check . && uv run pyright` → clean (0 errors).
- Story E2E `uv run pytest tests/e2e/test_llm_history.py -v --no-cov` → **3 passed** (grounded follow-up, cold-start no-history, deflected follow-up — prior Q/A + prior thinking proven byte-exact via mock echo).
- Regression suites in isolation (AGENTS.md rule 9): test_chat_rag, test_chat_history, test_agent_document_tools, test_harness_aligned_tools, test_stop_generation, test_retry_answer, test_response_to_docs → **all pass** (exit 0, no failures).
- Commit `055c0b5 feat(rag): pass chat history with prior thinking to the LLM`: atomic, covers all phase code, **no gpg signature** (`--no-gpg-sign` honored); no uncommitted app code; `echo my history` collides with no existing phrase/fixture.
- Completion criteria: 1) both branches carry history + reasoning ✅ 2) no-history byte-identical (integration pins + green regressions) ✅ 3) log field + suite + coverage + lint ✅ 4) commit + phase dir in `complete/` ✅.
- Notable: running multiple e2e suites in ONE pytest process causes a port-8123 bind clash (several suites by design boot their own app on `APP_PORT`, assuming per-suite isolation). Pre-existing infra property, not a phase-74 defect — the mandated isolation gate passes everywhere.
- Next pending phase: **75_save_doc_full_session**.
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 23%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 37%]
........................................................................ [ 41%]
........................................................................ [ 46%]
........................................................................ [ 51%]
........................................................................ [ 55%]
........................................................................ [ 60%]
........................................................................ [ 65%]
........................................................................ [ 69%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 88%]
........................................................................ [ 93%]
........................................................................ [ 97%]
................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 52 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2989 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,13 @@
All done. Final report:
**Phase 74 task 03 — complete**
- Implemented: mock `HISTORY_TRIGGER` (`echo my history`) with byte-stable stateless echo (`history: N prior messages; last answer tail: <24 chars>; thinking: yes|no`), checked before the `DEFLECT_MODE` branch; module docstring entry incl. the user/assistant-only history invariant; new E2E suite `tests/e2e/test_llm_history.py` (3 tests: grounded follow-up, cold-start, deflected follow-up — all byte-exact vs the persisted `bor.chat.v1` record).
- Gate questions verified against the app's real retrieval path (HIGH: 0.365/0.385 ≥ 0.30; deflected: 0.089, fts=0).
- E2E: `uv run pytest tests/e2e/test_llm_history.py -v --no-cov` → 3 passed (ran twice).
- Regressions in isolation, all green: chat_rag (3), chat_history (5), agent_document_tools (4), harness_aligned_tools (3), stop_generation (3), retry_answer (4), response_to_docs (4).
- `uv run pytest --cov=app --cov-report=term-missing` → 1547 passed, **99%** coverage (app/).
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors.
- Committed `055c0b5 feat(rag): pass chat history with prior thinking to the LLM` (`--no-gpg-sign`, 29 files; records the harness moves of 73/74 task files to `complete/`).
- Deviations: (1) per harness rules I left `todo/74_llm_chat_history/` (00_phase + 03) in place — the harness moves it on success (same pattern as phase 73); (2) Playwright 1.62 sync API has no `expect.poll` — used `page.wait_for_function` for the record wait.
- Next pending task: `.agents/phases/todo/75_save_doc_full_session/01_session_transcript.md`.
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 23%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 37%]
........................................................................ [ 41%]
........................................................................ [ 46%]
........................................................................ [ 51%]
........................................................................ [ 55%]
........................................................................ [ 60%]
........................................................................ [ 65%]
........................................................................ [ 69%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 88%]
........................................................................ [ 93%]
........................................................................ [ 97%]
................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 52 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2989 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,10 @@
All green. Final report:
**Phase 75 · Task 01 — `saveAsDoc` posts the full-session transcript**
- **`frontend/assets/app.js`**: added `buildSessionTranscript()` (next to `defaultDocTitle()`/`saveAsDoc()`) — one numbered section per user turn (`## N. <raw question>` + blank line + raw answer text), sections blank-line separated, trailing whitespace collapsed to a single final newline; only `m.who`/`m.text` travel (no thinking/sources/tune); orphan-brain and answerless-user edges handled. `saveAsDoc(btn)` now posts `body: buildSessionTranscript()`; dead `markdown` param dropped, single call site updated, `appendSaveAsDocButton(wrap, markdown)` signature kept; double-click guard + 201→`/doc-edit.html?draft=<token>` hand-off unchanged. Title/path rules untouched (A6).
- **Tests**: new unit pin `test_app_js_transcript_covers_the_whole_session` + updated `test_app_js_post_payload_and_navigation` in `tests/unit/test_save_as_doc_button.py`; phase-59 E2E body expectations moved to the single-turn transcript (new `transcript()` port helper) in `tests/e2e/test_response_to_docs.py` — push→git verification, guest/unconfigured pins unchanged; no backend change.
- **Builder byte-verified** via node harness against the real extracted function (single, multi, trailing-collapse, orphan, answerless cases).
- **Results**: `uv run pytest tests/e2e/test_response_to_docs.py -v --no-cov` → 4 passed; `uv run pytest` → 1548 passed; `uv run pytest --cov=app` → TOTAL 99% (>90%); `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors.
- **Decisions**: E2E expects the server-stored (`.strip()`-ed) transcript — the builder's final `\n` is stripped by the draft API, so the assertion helper omits it; no commit (phase 75's atomic commit belongs to task 02 per the phase overview).
- **Next pending task**: `.agents/phases/todo/75_save_doc_full_session/02_e2e_full_session_save.md`
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 23%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 37%]
........................................................................ [ 41%]
........................................................................ [ 46%]
........................................................................ [ 51%]
........................................................................ [ 55%]
........................................................................ [ 60%]
........................................................................ [ 65%]
........................................................................ [ 69%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 88%]
........................................................................ [ 93%]
........................................................................ [ 97%]
.................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 52 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2989 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK