Files
brain-of-reese/.agents/phases/complete/145_git_source_links/00_phase.md
T
ducoterra 32a3ec4f5e
Build and Push Containers / build-and-push-app (push) Successful in 2m23s
Build and Push Containers / build-and-push-db (push) Successful in 13s
phase: 145_git_source_links
**Phase 145 final verification pass — all green, no code changes needed**

- **Verified (no defects found):** resolver `origin_for_source` + `_url_basename` (scp/`.git`/trailing-slash, sanitize, ambiguity WARNING); `origin:`/`branch:` lines in all four read shapes via shared `_origin_lines`; HIGH `<tools>` origin rule (verbatim + only-byte-change pins); `DocContent.origin_url/origin_branch` (absent-never-null serializer); `renderDocument` "Original" anchor (copyable verbatim URL, `_blank`/`noopener`, branch in title); mock scripted origin-link flow (disjoint trigger — grep-verified vs all other E2E files)
- **Tests:** `uv run pytest --cov=app --cov-report=term-missing` → 3402 passed, `app/` coverage **99%** (>90% gate); `uv run pytest tests/e2e/test_git_source_links.py -v --no-cov` → **4 passed** in isolation; `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings
- **Completion criteria:** (1) git read results carry origin/branch, local byte-identical — ✅ (unit pins + E2E control); (2) HIGH `<tools>` rule pinned, LOW byte-identical — ✅; (3) modal AND /document.html show Original link, branch in label when pinned, nothing for local — ✅; (4) full suite green + coverage + E2E + lint/types — ✅; (5) no completed-phase behavior change (read header line 1, mock prefix, done frame/SourceRef untouched, LOW sha) — ✅
- **Extra regression check:** re-ran shared-mock E2E suites `test_read_line_ranges.py` (2 passed) and `test_document_viewer.py` (8 passed) — no cross-suite regressions from the `mock_llm.py` addition
- **Documentation:** README.md reviewed in full (env table ⊇ `.env.example`, pages/routes, config defaults, scripts, 17-color palette, 300 s guard, 2 s sync poll, phase-145 provenance section matches implementation) and AGENTS.md (72rem container, no-CDN test, smoke test, gitignore, gates) — **verified consistent, no changes needed**
- **Notable:** phase-116 screenshot PNG was regenerated during this phase's E2E runs (byte re-encode only, visually identical, correct no-anchor local-source meta row) — left in tree for the harness commit
- **Next pending phase:** `146_search_tool_scope` (then 147, 148, 149)
2026-10-01 11:13:43 -04:00

8.8 KiB
Raw Blame History

Phase 145 — the answer points at the original git source: the model links it, the modal shows it

Source: TODO.md L7 — "there always needs to be a link to the original git source if possible. I've asked a few questions like 'how does the phased execution process work for pi.dev?' And the correct answer is an explanation and then point to the skills repo in the answer so I can go download the skills for my personal use. It's important to provide the full source so users get the complete answer at once rather than extracting it bit by bit with lots of questions." Story: n/a (TODO-derived phase, Protocol B append). Context: app/rag/agent.py — the read branch's result shapes (L1630–1680: Document {source}/{path}:\n + date: …\n + image marker + content; the E2E mock's _READ_RESULT_PREFIX = the FIRST line only — tests/e2e/mock_llm.py L1412 — so a new line BELOW the first line keeps the mock contract); app/models.py — GitSource (url L289, kind L292, token L316 — the PAT, never served, branch L333 — phase 135, NULL = default branch); app/rag/git_sources.py — sanitize_url (L150: the phase-121 credential-free output mask — strips userinfo, byte-identical for clean URLs) and clone_url_for (the single credential-injection site); app/schemas.py — DocContent (the GET /api/documents/content payload — the phase-106 created_at field is the recent optional-metadata precedent) and SourceRef (UNCHANGED this phase — the done frame stays byte-identical, the origin rides the content payload instead); app/api/docs.py — the content endpoint (the (source, path) lookup, percent-encoding); frontend/assets/document.js — renderDocument (L172+: the shared meta-badge row — metaBadge/pathCode idioms — used by BOTH the doc modal and the full page); app/rag/prompts.py — the <tools> section (L162+: the ls/read/grep teaching, HIGH prompt only); app/rag/retriever.py — source_ref_with_image (untouched). Locked (owner-confirmed 2026-09-30, chat): the model learns the origin from a new origin:/branch: line in read results (git sources only, credentials stripped) and is taught to include the original-source link in its answer — it builds the browse URL itself (no fragile server-side GitHub/Gitea URL construction); the doc modal + document page get the "Original" external link (one renderDocument site, fed by the content payload); local (non-git) sources get no link.

Objective

An answer grounded in a git-sourced document comes with the way to the complete original in ONE step: the read result tells the model the repository origin (and pinned branch), the prompt teaches it to include the original-source link in its answer text (so it works in the UI AND through the API tokens), and the document modal + full page show the same origin as a visible "Original" external link.

Dependencies

  • 144_modal_back_button (todo) — pipeline order only; code touch (the modal's meta row) is the same renderDocument surface, no ordering risk.

Design (shared by all tasks — the executor reads this, not the chat)

  • The origin resolver (task 01): app/rag/git_sources.py (the GitSource-domain home) — origin_for_source(db, source_name) -> tuple[str, str | None] | None: the source's directory basename (the importer's source = root.name) mapped to a kind = "git" row by the row's URL basename — the URL's path component, trailing / stripped, .git suffix stripped, last path segment (scp-style git@host:owner/repo.git → the part after : first; sanitize_url applied to the result so a legacy embedded-credential row never leaks; branch returned as-is — NULL means the default branch). EXACTLY ONE match → (sanitized_url, branch); zero or TWO-OR-MORE matches (two repos with the same basename) → None (no line — never guess; the ambiguity is logged at WARNING with the sanitized names). No caching (the table is a handful of rows; a read's extra query is trivial).
  • The read result (task 01): a git-sourced document's read result gains, IMMEDIATELY AFTER the date: line (before the image marker — the marker keeps its phase-122 position relative to the content): origin: {sanitized_url} and, when a branch is pinned, branch: {branch} — one line each, no URL for local sources, no lines at all for a source with no unambiguous git row (the result is byte-identical to pre-phase for every non-git doc — the existing pins pass unchanged; the mock's _READ_RESULT_PREFIX first-line contract is untouched — the new lines sit BELOW the header). The plain-read, truncated-plain, range-read, and truncated-range shapes all carry the lines at the same position (one insertion helper — the four result builders share it).
  • The teaching (task 02): the <tools> section gains the citation-source rule (the read teaching's natural home — the model answers from what it READ): when an answer uses a document whose read result carries an origin: line, the answer INCLUDES the original source — a link to the repository (the origin line) with the document's path (from the Document … header) named, and the pinned branch when one is shown — formatted as the repository's file-view URL where the model can tell it (GitHub/Gitea/GitLab …/blob/<branch>/<path>), else the repo URL + path as text. The point (the owner's words, in the model's language): the user should get the complete source at once — one link, not a second question. Local-sourced documents carry no origin line — nothing to add. The SUGGEST_INTRO citation discipline (cite only what you read) is UNCHANGED — this rule extends the cited documents' answers, it does not widen the cite set.
  • The UI (task 03): DocContent (the content payload) gains origin_url: str | None = None + origin_branch: str | None = None (the resolver's output for the document's source — NULL/absent for local sources, the image_url-style optional-metadata shape; a null field in the content payload is fine — the omission rule binds the SSE frames, not this payload). renderDocument (document.js L172+) — when doc.origin_url is present, the meta row gains, after the path code: an "Original" anchor (<a> — createElement, the never-build-HTML rule; href = doc.origin_url, target="_blank" rel="noopener" — the modal's "Full page" escape-hatch idiom; textContent = doc.origin_url VERBATIM — the copyable-link contract — in the doc-path ellipsis style, the full URL in a title attribute that also carries — branch: <name> when origin_branch is present) — ONE site: the modal AND the full page show it (both call renderDocument).
  • The E2E (task 04): a git row seeded over the fixture source's name (the resolver is name-based — a kind="git" row whose URL basename matches the fixture source name makes the fixture docs "git-sourced" for this phase's purposes), a scripted mock flow whose final answer carries the repo URL, and the modal's meta anchor asserted.

Tasks

  1. 01_origin_resolution.md — the resolver + the read-result origin:/branch: lines (the four read shapes).
  2. 02_prompt_teaching.md — the <tools> original-source rule + the prompt pins.
  3. 03_content_payload_render_link.md — DocContent fields + the content endpoint + the renderDocument "Original" anchor (modal + full page).
  4. 04_e2e_source_link.md — the dedicated E2E (the answer carries the repo link; the modal shows the Original anchor; non-git docs stay byte-identical).

Testing & Quality

  • Unit: the resolver (the mapping, the sanitization, the ambiguity, the scp-form URL, the .git suffix, the no-row case); the read-result insertion (four shapes × {git, git+branch, local, ambiguous} — non-git byte-identical pins); the prompt copy pin; the payload/endpoint; the renderer (the anchor present/absent, the branch label, the ellipsis title).
  • Integration: the content endpoint returns origin_url for a seeded git row (and null for a plain local source).
  • E2E: one dedicated Playwright file, run in isolation, mock-only.
  • Coverage: >90% on new/modified code (app/).

Completion Criteria

  • a git-sourced read result carries origin: <sanitized url> (+ branch: when pinned); every local-source read result is byte-identical to pre-phase
  • the HIGH prompt's <tools> section teaches the original-source link rule (pinned verbatim); the LOW prompt is byte-identical
  • the doc modal AND /document.html show the "Original" external link for git-sourced docs (the branch in the label when pinned); nothing new for local docs
  • uv run pytest --cov=app --cov-report=term-missing green, app/ coverage >90%; uv run pytest tests/e2e/test_git_source_links.py -v --no-cov green in isolation; uv run ruff check . && uv run pyright clean
  • no behavior change in completed phases (the read header's first line, the mock's parse, the done frame, the LOW prompt, the non-git read results)