phase: 191_source_name_labels_tags
All gates re-verified against a stable tree. Report: **Verified (all 6 tasks in `complete/`; this was the final pass)** - `SourceLabel`+alembic `0030` (dev DB `alembic current` = `0030 (head)`, table present, 0 rows); admin-only `GET/PUT/DELETE /api/source-labels` (`require_admin` router-wide, `resolve_source_name`, partial-update caps/404s); Sources-page "Names, labels and tags" card (read-only derived-name `<code>`, per-row `saveInFlight`, 404 note that survives the settle, chips reuse `.git-source-hidden-count`); `question_name_hits` D2 rule; ONE `all_labels` read per turn in `chat.py` feeding both the `source_name_hit` flag and the prompt surfaces; `source_name_hit_bonus=0.005` in `_selection_order`; ` name=`/` label=` in `build_high_prompt` + `build_overview_prompt(names=…)`. - **Completion criteria:** E2E `tests/e2e/test_source_name_labels.py` ✅ 7 passed (arms a–e: editor all kinds, control/named question, rename-never-breaks-identity, 404 note, reset) · full suite ✅ · coverage ✅ · README/AGENTS ✅ · phase dir in `complete/` ✅ · commit left to the harness. - **Gates:** `uv run pytest --cov=app --cov-report=term-missing` → **exit 0, TOTAL 7160 stmts / 16 missed = 99%** (>90%) · `uv run pytest tests/e2e/test_source_name_labels.py -v --no-cov` → **7 passed, exit 0** · regression E2E isolated, exit 0: `test_document_viewer`, `test_admin_auth`, `test_api_tokens`, `test_git_sources_admin`, `test_kb_tree`, `test_local_directory_sources` · `uv run ruff check .` → All checks passed · `uv run pyright` → 0 errors. - **Battery (task 04 criterion):** `uv run python -m scripts.eval_retrieval --from-file tests/fixtures/retrieval_battery.txt` at default vs `BOR_SOURCE_NAME_HIT_BONUS=0` → **`diff` empty (line-for-line identical)**, matching the recorded arms (dev KB is empty, so every question deflects; the observable boost is unit/E2E-pinned). - **Documentation:** README — fixed the `/git-sources.html` rows (nav label is **Sources**) and cross-linked the phase-191 card from that section; full audit otherwise clean: all 86 `BOR_*` settings documented with defaults matching `app/config.py`, 0 broken file/dir references and 0 broken anchors in README+AGENTS, AGENTS rule 17 re-checked line-by-line against the code. - **Notable / deviations:** (1) a **concurrent pipeline process** (`phased-execution/progress.mjs` for `191_…_00_phase`) was mutating this same working tree mid-pass — it rewrote `tests/e2e/test_source_name_labels.py` (11→7 tests), `frontend/assets/git-sources.js`, `tests/unit/test_source_labels_editor_ui.py` and `AGENTS.md` at 05:27; every gate above was (re-)run after those edits with the tree no source file newer than the green full run. (2) The task-06 report/phase text claims regression suites `test_chat_token_gate.py`, `test_git_sources_page.py`, `test_rag_browser.py` are green — **those files have never existed in git history** (those runs exit 4); I ran the real equivalents listed above instead (phase files are not mine to edit). (3) **No live changes**: the VM DB already has `source_labels` (0 rows) but the deployed app predates this uncommitted code, so deploying `/api/source-labels` to the live service is the owner's post-commit step, not part of this phase's criteria. - **Next pending phase:** none — `191_source_name_labels_tags` is the only entry in `.agents/phases/todo/`.
This commit is contained in:
+51
-1
@@ -258,7 +258,7 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from collections.abc import AsyncIterator, Sequence
|
||||
from collections.abc import AsyncIterator, Mapping, Sequence
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -301,6 +301,11 @@ from app.rag.retriever import (
|
||||
weak_hit_titles,
|
||||
)
|
||||
from app.rag.scaffolding import ScaffoldingFilter # phase 71: the streaming filter
|
||||
from app.rag.source_labels import (
|
||||
SourceLabelView,
|
||||
all_labels,
|
||||
question_name_hits,
|
||||
)
|
||||
from app.rag.suggestions import derive_suggestions
|
||||
from app.rag.summarizer import (
|
||||
IMAGE_FALLBACK_MIME,
|
||||
@@ -461,6 +466,7 @@ def plan_turn(
|
||||
notes: Sequence[str] | None = None,
|
||||
kb_overview: str | None = None,
|
||||
folder_lines: Sequence[str] = (),
|
||||
names: Mapping[str, SourceLabelView] | None = None,
|
||||
) -> TurnPlan:
|
||||
"""Apply the honesty gate (A8, revised) and assemble prompt + context.
|
||||
|
||||
@@ -502,6 +508,15 @@ def plan_turn(
|
||||
phase-118 shape. The :class:`TurnPlan`, the durable record, and the
|
||||
per-turn log line are untouched.
|
||||
|
||||
*names* (phase 191, D4): the source-name → ``SourceLabelView`` map of
|
||||
the owner's label rows — the SAME single ``all_labels`` read the
|
||||
endpoint performs for the task-04 selection boost (ONE query per
|
||||
turn). The HIGH branch hands it to ``build_high_prompt`` (the
|
||||
additive ``name`` / ``label`` attributes of the ``<document>``
|
||||
blocks); the LOW branch IGNORES it — the deflected prompt stays
|
||||
byte-identical. ``None`` ⇒ every block is byte-identical to
|
||||
pre-phase.
|
||||
|
||||
``summary_hits`` (phase 30; phase 118 redefinition) counts the hit
|
||||
chunks with ``is_summary`` whose parent document is in the
|
||||
SUGGESTED set — both the HIGH and the LOW branch record it.
|
||||
@@ -561,6 +576,10 @@ def plan_turn(
|
||||
# byte-identical to pre-phase (no clause, and agent_tools
|
||||
# offers four tools, not five).
|
||||
show_text=settings.answer_text,
|
||||
# Phase 191 (D4, task 05): the owner's source names ride the
|
||||
# same map the boost flags came from — one all_labels read
|
||||
# per turn, no second label query here.
|
||||
names=names,
|
||||
),
|
||||
suggested,
|
||||
related_docs,
|
||||
@@ -860,6 +879,33 @@ async def chat(
|
||||
kb_overview = load_kb_overview(step_db)
|
||||
with SessionLocal() as step_db:
|
||||
chunks = retrieve(step_db, request.message, question_vec)
|
||||
# Phase 191 (D3 task 04 + D4 task 05): ONE
|
||||
# all_labels read per turn feeds BOTH surfaces —
|
||||
# the source-name hit flags here AND the prompt
|
||||
# surfaces (the map rides into plan_turn →
|
||||
# build_high_prompt's name/label attributes). The
|
||||
# hit set is the pure D2 rule per view, then a
|
||||
# loop over the few dozen retrieved chunks against
|
||||
# the handful of hit sources (never a per-chunk
|
||||
# query — task 01's question_name_hit_sources is
|
||||
# this same pair, inlined here so the read is
|
||||
# shared). The flags ride the chunk objects into
|
||||
# EVERY selection call below (plan_turn's walk
|
||||
# included); the source_name_hit_bonus itself is
|
||||
# pulled from settings inside the selection
|
||||
# helpers. No label rows → empty map → no flags
|
||||
# and no name attributes → every surface is the
|
||||
# pre-phase one; the per-turn log line is
|
||||
# untouched (the boost is invisible to it).
|
||||
labels = all_labels(step_db)
|
||||
source_name_hits = {
|
||||
view.source
|
||||
for view in labels.values()
|
||||
if question_name_hits(view, request.message)
|
||||
}
|
||||
for rc in chunks:
|
||||
if rc.document.source in source_name_hits:
|
||||
rc.source_name_hit = True
|
||||
# Phase 119 (D3, LOCKED A4): the suggested-folder
|
||||
# context — the SAME deterministic suggested walk
|
||||
# plan_turn performs internally (one extra cheap
|
||||
@@ -880,6 +926,10 @@ async def chat(
|
||||
notes=steering_notes,
|
||||
kb_overview=kb_overview,
|
||||
folder_lines=folder_lines,
|
||||
# Phase 191 (D4, task 05): the SAME single read —
|
||||
# the HIGH prompt learns the name→docs mapping
|
||||
# from these views (no second label query).
|
||||
names=labels,
|
||||
)
|
||||
except Exception: # noqa: BLE001 — DB failure mid-turn
|
||||
logger.exception(
|
||||
|
||||
Reference in New Issue
Block a user